From 34f3653f578fdf0ff4d7509dad3166082d2370ab Mon Sep 17 00:00:00 2001 From: Lee Surprenant Date: Tue, 15 Mar 2022 16:42:45 -0400 Subject: [PATCH] issue #3390 - whole-system interactions use config * Introduce the ResourceTypeName enum in fhir-core and ResourcesConfigAdapter in fhir-config * Use ResourcesConfigAdapter from whole-system search, whole-system history, $everything, and $export I also started on #3319 - system-search now supports multiple instances of the `_type` parameter Signed-off-by: Lee Surprenant --- .../patient/PatientExportPartitionMapper.java | 6 +- .../com/ibm/fhir/config/FHIRConfigHelper.java | 38 +- .../java/com/ibm/fhir/config/Interaction.java | 36 + .../fhir/config/ResourcesConfigAdapter.java | 114 ++ .../test/ResourcesConfigAdapterTest.java | 39 + .../com/ibm/fhir/core/ResourceTypeName.java | 1170 +++++++++++++++++ .../persistence/util/FHIRPersistenceUtil.java | 31 +- .../util/PartitionedSequentialKey.java | 3 +- .../util/FHIRPersistenceUtilTest.java | 108 +- .../com/ibm/fhir/search/SearchConstants.java | 2 +- .../search/compartment/CompartmentCache.java | 18 +- .../search/compartment/CompartmentHelper.java | 6 +- .../ibm/fhir/search/util/SearchHelper.java | 47 +- .../compartment/CompartmentCacheTest.java | 15 +- .../compartment/CompartmentHelperTest.java | 5 +- .../search/test/TypeParameterParseTest.java | 28 +- .../spi/operation/AbstractOperation.java | 45 +- .../server/spi/operation/FHIROperation.java | 6 + .../test/ServerResolveFunctionTest.java | 29 +- ...licyEnforcementPersistenceInterceptor.java | 42 +- .../generator/FHIROpenApiGenerator.java | 6 +- .../generator/FHIRSwaggerGenerator.java | 6 +- .../operation/bulkdata/ExportOperation.java | 3 +- .../bulkdata/client/BulkDataClient.java | 34 +- .../processor/ExportImportBulkData.java | 5 +- .../processor/impl/ExportImportImpl.java | 7 +- .../bulkdata/util/BulkDataExportUtil.java | 154 +-- .../bulkdata/util/BulkDataImportUtil.java | 6 +- .../bulkdata/util/BulkDataExportUtilTest.java | 13 +- .../everything/EverythingOperation.java | 26 +- 30 files changed, 1725 insertions(+), 323 deletions(-) create mode 100644 fhir-config/src/main/java/com/ibm/fhir/config/Interaction.java create mode 100644 fhir-config/src/main/java/com/ibm/fhir/config/ResourcesConfigAdapter.java create mode 100644 fhir-config/src/test/java/com/ibm/fhir/config/test/ResourcesConfigAdapterTest.java create mode 100644 fhir-core/src/main/java/com/ibm/fhir/core/ResourceTypeName.java diff --git a/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/jbatch/export/patient/PatientExportPartitionMapper.java b/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/jbatch/export/patient/PatientExportPartitionMapper.java index 7b82fdf013b..97ace073947 100644 --- a/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/jbatch/export/patient/PatientExportPartitionMapper.java +++ b/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/jbatch/export/patient/PatientExportPartitionMapper.java @@ -10,6 +10,7 @@ import java.util.Arrays; import java.util.List; import java.util.Properties; +import java.util.Set; import javax.batch.api.partition.PartitionMapper; import javax.batch.api.partition.PartitionPlan; @@ -34,6 +35,7 @@ import com.ibm.fhir.persistence.helper.FHIRTransactionHelper; import com.ibm.fhir.search.compartment.CompartmentHelper; + @Dependent public class PatientExportPartitionMapper implements PartitionMapper { @@ -59,9 +61,9 @@ public PartitionPlan mapPartitions() throws Exception { // By default we're in the Patient Compartment, if we have a valid context // which has a resourceType specified, it's valid as the operation has already checked. - List resourceTypes = compartmentHelper.getCompartmentResourceTypes("Patient"); + Set resourceTypes = compartmentHelper.getCompartmentResourceTypes("Patient"); if (ctx.getFhirResourceTypes() != null ) { - resourceTypes = Arrays.asList(ctx.getFhirResourceTypes().split("\\s*,\\s*")); + resourceTypes = Set.of(ctx.getFhirResourceTypes().split("\\s*,\\s*")); } // Register the context to get the right configuration. diff --git a/fhir-config/src/main/java/com/ibm/fhir/config/FHIRConfigHelper.java b/fhir-config/src/main/java/com/ibm/fhir/config/FHIRConfigHelper.java index f64c844dafd..cba4b5e0269 100644 --- a/fhir-config/src/main/java/com/ibm/fhir/config/FHIRConfigHelper.java +++ b/fhir-config/src/main/java/com/ibm/fhir/config/FHIRConfigHelper.java @@ -6,13 +6,12 @@ package com.ibm.fhir.config; -import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import com.ibm.fhir.config.PropertyGroup.PropertyEntry; -import com.ibm.fhir.exception.FHIRException; import jakarta.json.JsonValue; @@ -163,38 +162,19 @@ private static T getTypedProperty(Class expectedDataType, String property } /** - * This method returns the list of supported resource types - * @return a list of resource types that isn't null + * Get the set of supported resource types for tenantId in the FHIRRequestContext + * @return an immutable set of resource type names that isn't null + * @throws IllegalStateException if there is an unexpected issue while processing the config */ - public static List getSupportedResourceTypes() throws FHIRException { - List result = new ArrayList<>(); - + public static Set getSupportedResourceTypes() { PropertyGroup rsrcsGroup = FHIRConfigHelper.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES); - List rsrcsEntries; try { - rsrcsEntries = rsrcsGroup.getProperties(); - - if (rsrcsEntries != null && !rsrcsEntries.isEmpty()) { - for (PropertyEntry rsrcsEntry : rsrcsEntries) { - String name = rsrcsEntry.getName(); - - // Ensure we skip over the special property "open" and process only the others - // and skip the abstract types Resource and DomainResource - // It would be nice to be able to verify if the resource names were valid, but - // not possible at this layer of the code. - if (!FHIRConfiguration.PROPERTY_FIELD_RESOURCES_OPEN.equals(name) && - !"Resource".equals(name) && - !"DomainResource".equals(name)) { - result.add(name); - } - } - } + ResourcesConfigAdapter configAdapter = new ResourcesConfigAdapter(rsrcsGroup); + return configAdapter.getSupportedResourceTypes(); } catch (Exception e) { - log.fine("FHIRConfigHelper.getSupportedResourceTypes is configured with no " - + "resources in the server config file or is not configured properly"); + log.throwing(FHIRConfigHelper.class.getName(), "getSupportedResourceTypes", e); + throw new IllegalStateException(e); } - - return result; } /** diff --git a/fhir-config/src/main/java/com/ibm/fhir/config/Interaction.java b/fhir-config/src/main/java/com/ibm/fhir/config/Interaction.java new file mode 100644 index 00000000000..9a2189a40f5 --- /dev/null +++ b/fhir-config/src/main/java/com/ibm/fhir/config/Interaction.java @@ -0,0 +1,36 @@ +/* + * (C) Copyright IBM Corp. 2022 + * + * SPDX-License-Identifier: Apache-2.0 + */ +package com.ibm.fhir.config; + +public enum Interaction { + CREATE("create"), + DELETE("delete"), + HISTORY("history"), + PATCH("patch"), + READ("read"), + SEARCH("search"), + UPDATE("update"), + VREAD("vread"); + + private final String value; + + Interaction(String value) { + this.value = value; + } + + public String value() { + return value; + } + + public static Interaction from(String value) { + for (Interaction interaction : Interaction.values()) { + if (interaction.value.equals(value)) { + return interaction; + } + } + throw new IllegalArgumentException(value); + } +} \ No newline at end of file diff --git a/fhir-config/src/main/java/com/ibm/fhir/config/ResourcesConfigAdapter.java b/fhir-config/src/main/java/com/ibm/fhir/config/ResourcesConfigAdapter.java new file mode 100644 index 00000000000..56ba11213b1 --- /dev/null +++ b/fhir-config/src/main/java/com/ibm/fhir/config/ResourcesConfigAdapter.java @@ -0,0 +1,114 @@ +/* + * (C) Copyright IBM Corp. 2022 + * + * SPDX-License-Identifier: Apache-2.0 + */ +package com.ibm.fhir.config; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import com.ibm.fhir.config.PropertyGroup.PropertyEntry; +import com.ibm.fhir.core.ResourceTypeName; + +/** + * An abstraction for the ibm-fhir-server fhirServer/resources property group + */ +public class ResourcesConfigAdapter { + public static final Logger log = Logger.getLogger(ResourcesConfigAdapter.class.getName()); + + public static final Set ALL_CONCRETE_TYPES = Arrays.stream(ResourceTypeName.values()) + .filter(v -> v != ResourceTypeName.RESOURCE && v != ResourceTypeName.DOMAIN_RESOURCE) + .map(v -> v.value()) + .collect(Collectors.toSet()); + + private final Set supportedTypes; + private final Map> typesByInteraction = new HashMap<>(); + + public ResourcesConfigAdapter(PropertyGroup resourcesConfig) throws Exception { + supportedTypes = computeSupportedResourceTypes(resourcesConfig); + + if (resourcesConfig == null) { + for (Interaction interaction : Interaction.values()) { + typesByInteraction.put(interaction, supportedTypes); + } + return; + } + + for (String resourceType : supportedTypes) { + List interactions = resourcesConfig.getStringListProperty(resourceType + "/" + FHIRConfiguration.PROPERTY_FIELD_RESOURCES_INTERACTIONS); + if (interactions == null) { + interactions = resourcesConfig.getStringListProperty("Resource/" + FHIRConfiguration.PROPERTY_FIELD_RESOURCES_INTERACTIONS); + } + + if (interactions == null) { + for (Interaction interaction : Interaction.values()) { + typesByInteraction.computeIfAbsent(interaction, k -> new LinkedHashSet<>()).add(resourceType); + } + continue; + } + + for (String interactionString : interactions) { + Interaction interaction = Interaction.from(interactionString); + typesByInteraction.computeIfAbsent(interaction, k -> new LinkedHashSet<>()).add(resourceType); + } + } + } + + /** + * @return an immutable, non-null set of concrete supported resource types + * @throws Exception + */ + public Set getSupportedResourceTypes() { + return supportedTypes; + } + + /** + * @return an immutable, non-null set of concrete resource types that are configured for the given interaction + */ + public Set getSupportedResourceTypes(Interaction interaction) { + return typesByInteraction.get(interaction); + } + + /** + * Construct the list of concrete supported resource types from the passed configuration + * + * @param resourcesConfig + * @return + * @throws Exception + */ + private Set computeSupportedResourceTypes(PropertyGroup resourcesConfig) throws Exception { + if (resourcesConfig == null || resourcesConfig.getBooleanProperty("open", true)) { + return ALL_CONCRETE_TYPES; + } + + Set result = new LinkedHashSet(); + for (PropertyEntry rsrcsEntry : resourcesConfig.getProperties()) { + String name = rsrcsEntry.getName(); + + // Ensure we skip over the special property "open" + // and skip the abstract types Resource and DomainResource + if (FHIRConfiguration.PROPERTY_FIELD_RESOURCES_OPEN.equals(name) || + "Resource".equals(name) || + "DomainResource".equals(name)) { + continue; + } + + if (ALL_CONCRETE_TYPES.contains(name)) { + result.add(name); + } else if (log.isLoggable(Level.FINE)) { + log.fine("Configured resource type '" + name + "' is not valid."); + } + } + + return Collections.unmodifiableSet(result); + } +} diff --git a/fhir-config/src/test/java/com/ibm/fhir/config/test/ResourcesConfigAdapterTest.java b/fhir-config/src/test/java/com/ibm/fhir/config/test/ResourcesConfigAdapterTest.java new file mode 100644 index 00000000000..aeb37f10127 --- /dev/null +++ b/fhir-config/src/test/java/com/ibm/fhir/config/test/ResourcesConfigAdapterTest.java @@ -0,0 +1,39 @@ +/* + * (C) Copyright IBM Corp. 2022 + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.fhir.config.test; + +import static org.testng.Assert.assertEquals; + +import java.util.Set; + +import org.testng.annotations.Test; + +import com.ibm.fhir.config.Interaction; +import com.ibm.fhir.config.PropertyGroup; +import com.ibm.fhir.config.ResourcesConfigAdapter; + +import jakarta.json.Json; +import jakarta.json.JsonObject; + +public class ResourcesConfigAdapterTest { + @Test + public void testGetSupportedResourceTypes() throws Exception { + JsonObject json = Json.createObjectBuilder().build(); + PropertyGroup pg = new PropertyGroup(json); + ResourcesConfigAdapter resourcesConfigAdapter = new ResourcesConfigAdapter(pg); + + Set supportedResourceTypes = resourcesConfigAdapter.getSupportedResourceTypes(); + assertEquals(supportedResourceTypes.size(), 141); + + System.out.println(supportedResourceTypes); + + for (Interaction interaction : Interaction.values()) { + supportedResourceTypes = resourcesConfigAdapter.getSupportedResourceTypes(interaction); + assertEquals(supportedResourceTypes.size(), 141); + } + } +} diff --git a/fhir-core/src/main/java/com/ibm/fhir/core/ResourceTypeName.java b/fhir-core/src/main/java/com/ibm/fhir/core/ResourceTypeName.java new file mode 100644 index 00000000000..5eaf00e68c1 --- /dev/null +++ b/fhir-core/src/main/java/com/ibm/fhir/core/ResourceTypeName.java @@ -0,0 +1,1170 @@ +/* + * (C) Copyright IBM Corp. 2022 + * + * SPDX-License-Identifier: Apache-2.0 + */ +package com.ibm.fhir.core; + +/** + * Enum constants for all resource type names across all versions of HL7 FHIR + */ +public enum ResourceTypeName { + /** + * Resource + * + *

--- Abstract Type! ---This is the base resource type for everything. + */ + RESOURCE("Resource"), + + /** + * Binary + * + *

A resource that represents the data of a single raw artifact as digital content accessible in its native format. A + * Binary resource can contain any content, whether text, image, pdf, zip archive, etc. + */ + BINARY("Binary"), + + /** + * Bundle + * + *

A container for a collection of resources. + */ + BUNDLE("Bundle"), + + /** + * DomainResource + * + *

--- Abstract Type! ---A resource that includes narrative, extensions, and contained resources. + */ + DOMAIN_RESOURCE("DomainResource"), + + /** + * Account + * + *

A financial tool for tracking value accrued for a particular purpose. In the healthcare field, used to track + * charges for a patient, cost centers, etc. + */ + ACCOUNT("Account"), + + /** + * ActivityDefinition + * + *

This resource allows for the definition of some activity to be performed, independent of a particular patient, + * practitioner, or other performance context. + */ + ACTIVITY_DEFINITION("ActivityDefinition"), + + /** + * AdministrableProductDefinition + * + *

A medicinal product in the final form which is suitable for administering to a patient (after any mixing of + * multiple components, dissolution etc. has been performed). + */ + ADMINISTRABLE_PRODUCT_DEFINITION("AdministrableProductDefinition"), + + /** + * AdverseEvent + * + *

Actual or potential/avoided event causing unintended physical injury resulting from or contributed to by medical + * care, a research study or other healthcare setting factors that requires additional monitoring, treatment, or + * hospitalization, or that results in death. + */ + ADVERSE_EVENT("AdverseEvent"), + + /** + * AllergyIntolerance + * + *

Risk of harmful or undesirable, physiological response which is unique to an individual and associated with + * exposure to a substance. + */ + ALLERGY_INTOLERANCE("AllergyIntolerance"), + + /** + * Appointment + * + *

A booking of a healthcare event among patient(s), practitioner(s), related person(s) and/or device(s) for a + * specific date/time. This may result in one or more Encounter(s). + */ + APPOINTMENT("Appointment"), + + /** + * AppointmentResponse + * + *

A reply to an appointment request for a patient and/or practitioner(s), such as a confirmation or rejection. + */ + APPOINTMENT_RESPONSE("AppointmentResponse"), + + /** + * AuditEvent + * + *

A record of an event made for purposes of maintaining a security log. Typical uses include detection of intrusion + * attempts and monitoring for inappropriate usage. + */ + AUDIT_EVENT("AuditEvent"), + + /** + * Basic + * + *

Basic is used for handling concepts not yet defined in FHIR, narrative-only resources that don't map to an existing + * resource, and custom resources not appropriate for inclusion in the FHIR specification. + */ + BASIC("Basic"), + + /** + * BiologicallyDerivedProduct + * + *

A material substance originating from a biological entity intended to be transplanted or infused + *

into another (possibly the same) biological entity. + */ + BIOLOGICALLY_DERIVED_PRODUCT("BiologicallyDerivedProduct"), + + /** + * BodyStructure + * + *

Record details about an anatomical structure. This resource may be used when a coded concept does not provide the + * necessary detail needed for the use case. + */ + BODY_STRUCTURE("BodyStructure"), + + /** + * CapabilityStatement + * + *

A Capability Statement documents a set of capabilities (behaviors) of a FHIR Server for a particular version of + * FHIR that may be used as a statement of actual server functionality or a statement of required or desired server + * implementation. + */ + CAPABILITY_STATEMENT("CapabilityStatement"), + + /** + * CarePlan + * + *

Describes the intention of how one or more practitioners intend to deliver care for a particular patient, group or + * community for a period of time, possibly limited to care for a specific condition or set of conditions. + */ + CARE_PLAN("CarePlan"), + + /** + * CareTeam + * + *

The Care Team includes all the people and organizations who plan to participate in the coordination and delivery of + * care for a patient. + */ + CARE_TEAM("CareTeam"), + + /** + * CatalogEntry + * + *

Catalog entries are wrappers that contextualize items included in a catalog. + */ + CATALOG_ENTRY("CatalogEntry"), + + /** + * ChargeItem + * + *

The resource ChargeItem describes the provision of healthcare provider products for a certain patient, therefore + * referring not only to the product, but containing in addition details of the provision, like date, time, amounts and + * participating organizations and persons. Main Usage of the ChargeItem is to enable the billing process and internal + * cost allocation. + */ + CHARGE_ITEM("ChargeItem"), + + /** + * ChargeItemDefinition + * + *

The ChargeItemDefinition resource provides the properties that apply to the (billing) codes necessary to calculate + * costs and prices. The properties may differ largely depending on type and realm, therefore this resource gives only a + * rough structure and requires profiling for each type of billing code system. + */ + CHARGE_ITEM_DEFINITION("ChargeItemDefinition"), + + /** + * Citation + * + *

The Citation Resource enables reference to any knowledge artifact for purposes of identification and attribution. + * The Citation Resource supports existing reference structures and developing publication practices such as versioning, + * expressing complex contributorship roles, and referencing computable resources. + */ + CITATION("Citation"), + + /** + * Claim + * + *

A provider issued list of professional services and products which have been provided, or are to be provided, to a + * patient which is sent to an insurer for reimbursement. + */ + CLAIM("Claim"), + + /** + * ClaimResponse + * + *

This resource provides the adjudication details from the processing of a Claim resource. + */ + CLAIM_RESPONSE("ClaimResponse"), + + /** + * ClinicalImpression + * + *

A record of a clinical assessment performed to determine what problem(s) may affect the patient and before planning + * the treatments or management strategies that are best to manage a patient's condition. Assessments are often 1:1 with + * a clinical consultation / encounter, but this varies greatly depending on the clinical workflow. This resource is + * called "ClinicalImpression" rather than "ClinicalAssessment" to avoid confusion with the recording of assessment tools + * such as Apgar score. + */ + CLINICAL_IMPRESSION("ClinicalImpression"), + + /** + * ClinicalUseDefinition + * + *

A single issue - either an indication, contraindication, interaction or an undesirable effect for a medicinal + * product, medication, device or procedure. + */ + CLINICAL_USE_DEFINITION("ClinicalUseDefinition"), + + /** + * CodeSystem + * + *

The CodeSystem resource is used to declare the existence of and describe a code system or code system supplement + * and its key properties, and optionally define a part or all of its content. + */ + CODE_SYSTEM("CodeSystem"), + + /** + * Communication + * + *

An occurrence of information being transmitted; e.g. an alert that was sent to a responsible provider, a public + * health agency that was notified about a reportable condition. + */ + COMMUNICATION("Communication"), + + /** + * CommunicationRequest + * + *

A request to convey information; e.g. the CDS system proposes that an alert be sent to a responsible provider, the + * CDS system proposes that the public health agency be notified about a reportable condition. + */ + COMMUNICATION_REQUEST("CommunicationRequest"), + + /** + * CompartmentDefinition + * + *

A compartment definition that defines how resources are accessed on a server. + */ + COMPARTMENT_DEFINITION("CompartmentDefinition"), + + /** + * Composition + * + *

A set of healthcare-related information that is assembled together into a single logical package that provides a + * single coherent statement of meaning, establishes its own context and that has clinical attestation with regard to who + * is making the statement. A Composition defines the structure and narrative content necessary for a document. However, + * a Composition alone does not constitute a document. Rather, the Composition must be the first entry in a Bundle where + * Bundle.type=document, and any other resources referenced from Composition must be included as subsequent entries in + * the Bundle (for example Patient, Practitioner, Encounter, etc.). + */ + COMPOSITION("Composition"), + + /** + * ConceptMap + * + *

A statement of relationships from one set of concepts to one or more other concepts - either concepts in code + * systems, or data element/data element concepts, or classes in class models. + */ + CONCEPT_MAP("ConceptMap"), + + /** + * Condition + * + *

A clinical condition, problem, diagnosis, or other event, situation, issue, or clinical concept that has risen to a + * level of concern. + */ + CONDITION("Condition"), + + /** + * Consent + * + *

A record of a healthcare consumer’s choices, which permits or denies identified recipient(s) or recipient role(s) + * to perform one or more actions within a given policy context, for specific purposes and periods of time. + */ + CONSENT("Consent"), + + /** + * Contract + * + *

Legally enforceable, formally recorded unilateral or bilateral directive i.e., a policy or agreement. + */ + CONTRACT("Contract"), + + /** + * Coverage + * + *

Financial instrument which may be used to reimburse or pay for health care products and services. Includes both + * insurance and self-payment. + */ + COVERAGE("Coverage"), + + /** + * CoverageEligibilityRequest + * + *

The CoverageEligibilityRequest provides patient and insurance coverage information to an insurer for them to + * respond, in the form of an CoverageEligibilityResponse, with information regarding whether the stated coverage is + * valid and in-force and optionally to provide the insurance details of the policy. + */ + COVERAGE_ELIGIBILITY_REQUEST("CoverageEligibilityRequest"), + + /** + * CoverageEligibilityResponse + * + *

This resource provides eligibility and plan details from the processing of an CoverageEligibilityRequest resource. + */ + COVERAGE_ELIGIBILITY_RESPONSE("CoverageEligibilityResponse"), + + /** + * DetectedIssue + * + *

Indicates an actual or potential clinical issue with or between one or more active or proposed clinical actions for + * a patient; e.g. Drug-drug interaction, Ineffective treatment frequency, Procedure-condition conflict, etc. + */ + DETECTED_ISSUE("DetectedIssue"), + + /** + * Device + * + *

A type of a manufactured item that is used in the provision of healthcare without being substantially changed + * through that activity. The device may be a medical or non-medical device. + */ + DEVICE("Device"), + + /** + * DeviceDefinition + * + *

The characteristics, operational status and capabilities of a medical-related component of a medical device. + */ + DEVICE_DEFINITION("DeviceDefinition"), + + /** + * DeviceMetric + * + *

Describes a measurement, calculation or setting capability of a medical device. + */ + DEVICE_METRIC("DeviceMetric"), + + /** + * DeviceRequest + * + *

Represents a request for a patient to employ a medical device. The device may be an implantable device, or an + * external assistive device, such as a walker. + */ + DEVICE_REQUEST("DeviceRequest"), + + /** + * DeviceUseStatement + * + *

A record of a device being used by a patient where the record is the result of a report from the patient or another + * clinician. + */ + DEVICE_USE_STATEMENT("DeviceUseStatement"), + + /** + * DiagnosticReport + * + *

The findings and interpretation of diagnostic tests performed on patients, groups of patients, devices, and + * locations, and/or specimens derived from these. The report includes clinical context such as requesting and provider + * information, and some mix of atomic results, images, textual and coded interpretations, and formatted representation + * of diagnostic reports. + */ + DIAGNOSTIC_REPORT("DiagnosticReport"), + + /** + * DocumentManifest + * + *

A collection of documents compiled for a purpose together with metadata that applies to the collection. + */ + DOCUMENT_MANIFEST("DocumentManifest"), + + /** + * DocumentReference + * + *

A reference to a document of any kind for any purpose. Provides metadata about the document so that the document + * can be discovered and managed. The scope of a document is any seralized object with a mime-type, so includes formal + * patient centric documents (CDA), cliical notes, scanned paper, and non-patient specific documents like policy text. + */ + DOCUMENT_REFERENCE("DocumentReference"), + + /** + * Encounter + * + *

An interaction between a patient and healthcare provider(s) for the purpose of providing healthcare service(s) or + * assessing the health status of a patient. + */ + ENCOUNTER("Encounter"), + + /** + * Endpoint + * + *

The technical details of an endpoint that can be used for electronic services, such as for web services providing + * XDS.b or a REST endpoint for another FHIR server. This may include any security context information. + */ + ENDPOINT("Endpoint"), + + /** + * EnrollmentRequest + * + *

This resource provides the insurance enrollment details to the insurer regarding a specified coverage. + */ + ENROLLMENT_REQUEST("EnrollmentRequest"), + + /** + * EnrollmentResponse + * + *

This resource provides enrollment and plan details from the processing of an EnrollmentRequest resource. + */ + ENROLLMENT_RESPONSE("EnrollmentResponse"), + + /** + * EpisodeOfCare + * + *

An association between a patient and an organization / healthcare provider(s) during which time encounters may + * occur. The managing organization assumes a level of responsibility for the patient during this time. + */ + EPISODE_OF_CARE("EpisodeOfCare"), + + /** + * EventDefinition + * + *

The EventDefinition resource provides a reusable description of when a particular event can occur. + */ + EVENT_DEFINITION("EventDefinition"), + + /** + * Evidence + * + *

The Evidence Resource provides a machine-interpretable expression of an evidence concept including the evidence + * variables (eg population, exposures/interventions, comparators, outcomes, measured variables, confounding variables), + * the statistics, and the certainty of this evidence. + */ + EVIDENCE("Evidence"), + + /** + * EvidenceReport + * + *

The EvidenceReport Resource is a specialized container for a collection of resources and codable concepts, adapted + * to support compositions of Evidence, EvidenceVariable, and Citation resources and related concepts. + */ + EVIDENCE_REPORT("EvidenceReport"), + + /** + * EvidenceVariable + * + *

The EvidenceVariable resource describes an element that knowledge (Evidence) is about. + */ + EVIDENCE_VARIABLE("EvidenceVariable"), + + /** + * ExampleScenario + * + *

Example of workflow instance. + */ + EXAMPLE_SCENARIO("ExampleScenario"), + + /** + * ExplanationOfBenefit + * + *

This resource provides: the claim details; adjudication details from the processing of a Claim; and optionally + * account balance information, for informing the subscriber of the benefits provided. + */ + EXPLANATION_OF_BENEFIT("ExplanationOfBenefit"), + + /** + * FamilyMemberHistory + * + *

Significant health conditions for a person related to the patient relevant in the context of care for the patient. + */ + FAMILY_MEMBER_HISTORY("FamilyMemberHistory"), + + /** + * Flag + * + *

Prospective warnings of potential issues when providing care to the patient. + */ + FLAG("Flag"), + + /** + * Goal + * + *

Describes the intended objective(s) for a patient, group or organization care, for example, weight loss, restoring + * an activity of daily living, obtaining herd immunity via immunization, meeting a process improvement objective, etc. + */ + GOAL("Goal"), + + /** + * GraphDefinition + * + *

A formal computable definition of a graph of resources - that is, a coherent set of resources that form a graph by + * following references. The Graph Definition resource defines a set and makes rules about the set. + */ + GRAPH_DEFINITION("GraphDefinition"), + + /** + * Group + * + *

Represents a defined collection of entities that may be discussed or acted upon collectively but which are not + * expected to act collectively, and are not formally or legally recognized; i.e. a collection of entities that isn't an + * Organization. + */ + GROUP("Group"), + + /** + * GuidanceResponse + * + *

A guidance response is the formal response to a guidance request, including any output parameters returned by the + * evaluation, as well as the description of any proposed actions to be taken. + */ + GUIDANCE_RESPONSE("GuidanceResponse"), + + /** + * HealthcareService + * + *

The details of a healthcare service available at a location. + */ + HEALTHCARE_SERVICE("HealthcareService"), + + /** + * ImagingStudy + * + *

Representation of the content produced in a DICOM imaging study. A study comprises a set of series, each of which + * includes a set of Service-Object Pair Instances (SOP Instances - images or other data) acquired or produced in a + * common context. A series is of only one modality (e.g. X-ray, CT, MR, ultrasound), but a study may have multiple + * series of different modalities. + */ + IMAGING_STUDY("ImagingStudy"), + + /** + * Immunization + * + *

Describes the event of a patient being administered a vaccine or a record of an immunization as reported by a + * patient, a clinician or another party. + */ + IMMUNIZATION("Immunization"), + + /** + * ImmunizationEvaluation + * + *

Describes a comparison of an immunization event against published recommendations to determine if the + * administration is "valid" in relation to those recommendations. + */ + IMMUNIZATION_EVALUATION("ImmunizationEvaluation"), + + /** + * ImmunizationRecommendation + * + *

A patient's point-in-time set of recommendations (i.e. forecasting) according to a published schedule with optional + * supporting justification. + */ + IMMUNIZATION_RECOMMENDATION("ImmunizationRecommendation"), + + /** + * ImplementationGuide + * + *

A set of rules of how a particular interoperability or standards problem is solved - typically through the use of + * FHIR resources. This resource is used to gather all the parts of an implementation guide into a logical whole and to + * publish a computable definition of all the parts. + */ + IMPLEMENTATION_GUIDE("ImplementationGuide"), + + /** + * Ingredient + * + *

An ingredient of a manufactured item or pharmaceutical product. + */ + INGREDIENT("Ingredient"), + + /** + * InsurancePlan + * + *

Details of a Health Insurance product/plan provided by an organization. + */ + INSURANCE_PLAN("InsurancePlan"), + + /** + * Invoice + * + *

Invoice containing collected ChargeItems from an Account with calculated individual and total price for Billing + * purpose. + */ + INVOICE("Invoice"), + + /** + * Library + * + *

The Library resource is a general-purpose container for knowledge asset definitions. It can be used to describe and + * expose existing knowledge assets such as logic libraries and information model descriptions, as well as to describe a + * collection of knowledge assets. + */ + LIBRARY("Library"), + + /** + * Linkage + * + *

Identifies two or more records (resource instances) that refer to the same real-world "occurrence". + */ + LINKAGE("Linkage"), + + /** + * List + * + *

A list is a curated collection of resources. + */ + LIST("List"), + + /** + * Location + * + *

Details and position information for a physical place where services are provided and resources and participants + * may be stored, found, contained, or accommodated. + */ + LOCATION("Location"), + + /** + * ManufacturedItemDefinition + * + *

The definition and characteristics of a medicinal manufactured item, such as a tablet or capsule, as contained in a + * packaged medicinal product. + */ + MANUFACTURED_ITEM_DEFINITION("ManufacturedItemDefinition"), + + /** + * Measure + * + *

The Measure resource provides the definition of a quality measure. + */ + MEASURE("Measure"), + + /** + * MeasureReport + * + *

The MeasureReport resource contains the results of the calculation of a measure; and optionally a reference to the + * resources involved in that calculation. + */ + MEASURE_REPORT("MeasureReport"), + + /** + * Media + * + *

A photo, video, or audio recording acquired or used in healthcare. The actual content may be inline or provided by + * direct reference. + */ + MEDIA("Media"), + + /** + * Medication + * + *

This resource is primarily used for the identification and definition of a medication for the purposes of + * prescribing, dispensing, and administering a medication as well as for making statements about medication use. + */ + MEDICATION("Medication"), + + /** + * MedicationAdministration + * + *

Describes the event of a patient consuming or otherwise being administered a medication. This may be as simple as + * swallowing a tablet or it may be a long running infusion. Related resources tie this event to the authorizing + * prescription, and the specific encounter between patient and health care practitioner. + */ + MEDICATION_ADMINISTRATION("MedicationAdministration"), + + /** + * MedicationDispense + * + *

Indicates that a medication product is to be or has been dispensed for a named person/patient. This includes a + * description of the medication product (supply) provided and the instructions for administering the medication. The + * medication dispense is the result of a pharmacy system responding to a medication order. + */ + MEDICATION_DISPENSE("MedicationDispense"), + + /** + * MedicationKnowledge + * + *

Information about a medication that is used to support knowledge. + */ + MEDICATION_KNOWLEDGE("MedicationKnowledge"), + + /** + * MedicationRequest + * + *

An order or request for both supply of the medication and the instructions for administration of the medication to + * a patient. The resource is called "MedicationRequest" rather than "MedicationPrescription" or "MedicationOrder" to + * generalize the use across inpatient and outpatient settings, including care plans, etc., and to harmonize with + * workflow patterns. + */ + MEDICATION_REQUEST("MedicationRequest"), + + /** + * MedicationStatement + * + *

A record of a medication that is being consumed by a patient. A MedicationStatement may indicate that the patient + * may be taking the medication now or has taken the medication in the past or will be taking the medication in the + * future. The source of this information can be the patient, significant other (such as a family member or spouse), or a + * clinician. A common scenario where this information is captured is during the history taking process during a patient + * visit or stay. The medication information may come from sources such as the patient's memory, from a prescription + * bottle, or from a list of medications the patient, clinician or other party maintains. + * + *

The primary difference between a medication statement and a medication administration is that the medication + * administration has complete administration information and is based on actual administration information from the + * person who administered the medication. A medication statement is often, if not always, less specific. There is no + * required date/time when the medication was administered, in fact we only know that a source has reported the patient + * is taking this medication, where details such as time, quantity, or rate or even medication product may be incomplete + * or missing or less precise. As stated earlier, the medication statement information may come from the patient's + * memory, from a prescription bottle or from a list of medications the patient, clinician or other party maintains. + * Medication administration is more formal and is not missing detailed information. + */ + MEDICATION_STATEMENT("MedicationStatement"), + + /** + * MedicinalProductDefinition + * + *

Detailed definition of a medicinal product, typically for uses other than direct patient care (e.g. regulatory use, + * drug catalogs). + */ + MEDICINAL_PRODUCT_DEFINITION("MedicinalProductDefinition"), + + /** + * MessageDefinition + * + *

Defines the characteristics of a message that can be shared between systems, including the type of event that + * initiates the message, the content to be transmitted and what response(s), if any, are permitted. + */ + MESSAGE_DEFINITION("MessageDefinition"), + + /** + * MessageHeader + * + *

The header for a message exchange that is either requesting or responding to an action. The reference(s) that are + * the subject of the action as well as other information related to the action are typically transmitted in a bundle in + * which the MessageHeader resource instance is the first resource in the bundle. + */ + MESSAGE_HEADER("MessageHeader"), + + /** + * MolecularSequence + * + *

Raw data describing a biological sequence. + */ + MOLECULAR_SEQUENCE("MolecularSequence"), + + /** + * NamingSystem + * + *

A curated namespace that issues unique symbols within that namespace for the identification of concepts, people, + * devices, etc. Represents a "System" used within the Identifier and Coding data types. + */ + NAMING_SYSTEM("NamingSystem"), + + /** + * NutritionOrder + * + *

A request to supply a diet, formula feeding (enteral) or oral nutritional supplement to a patient/resident. + */ + NUTRITION_ORDER("NutritionOrder"), + + /** + * NutritionProduct + * + *

A food or fluid product that is consumed by patients. + */ + NUTRITION_PRODUCT("NutritionProduct"), + + /** + * Observation + * + *

Measurements and simple assertions made about a patient, device or other subject. + */ + OBSERVATION("Observation"), + + /** + * ObservationDefinition + * + *

Set of definitional characteristics for a kind of observation or measurement produced or consumed by an orderable + * health care service. + */ + OBSERVATION_DEFINITION("ObservationDefinition"), + + /** + * OperationDefinition + * + *

A formal computable definition of an operation (on the RESTful interface) or a named query (using the search + * interaction). + */ + OPERATION_DEFINITION("OperationDefinition"), + + /** + * OperationOutcome + * + *

A collection of error, warning, or information messages that result from a system action. + */ + OPERATION_OUTCOME("OperationOutcome"), + + /** + * Organization + * + *

A formally or informally recognized grouping of people or organizations formed for the purpose of achieving some + * form of collective action. Includes companies, institutions, corporations, departments, community groups, healthcare + * practice groups, payer/insurer, etc. + */ + ORGANIZATION("Organization"), + + /** + * OrganizationAffiliation + * + *

Defines an affiliation/assotiation/relationship between 2 distinct oganizations, that is not a part-of + * relationship/sub-division relationship. + */ + ORGANIZATION_AFFILIATION("OrganizationAffiliation"), + + /** + * PackagedProductDefinition + * + *

A medically related item or items, in a container or package. + */ + PACKAGED_PRODUCT_DEFINITION("PackagedProductDefinition"), + + /** + * Parameters + * + *

This resource is a non-persisted resource used to pass information into and back from an [operation](operations. + * html). It has no other use, and there is no RESTful endpoint associated with it. + */ + PARAMETERS("Parameters"), + + /** + * Patient + * + *

Demographics and other administrative information about an individual or animal receiving care or other health- + * related services. + */ + PATIENT("Patient"), + + /** + * PaymentNotice + * + *

This resource provides the status of the payment for goods and services rendered, and the request and response + * resource references. + */ + PAYMENT_NOTICE("PaymentNotice"), + + /** + * PaymentReconciliation + * + *

This resource provides the details including amount of a payment and allocates the payment items being paid. + */ + PAYMENT_RECONCILIATION("PaymentReconciliation"), + + /** + * Person + * + *

Demographics and administrative information about a person independent of a specific health-related context. + */ + PERSON("Person"), + + /** + * PlanDefinition + * + *

This resource allows for the definition of various types of plans as a sharable, consumable, and executable + * artifact. The resource is general enough to support the description of a broad range of clinical and non-clinical + * artifacts such as clinical decision support rules, order sets, protocols, and drug quality specifications. + */ + PLAN_DEFINITION("PlanDefinition"), + + /** + * Practitioner + * + *

A person who is directly or indirectly involved in the provisioning of healthcare. + */ + PRACTITIONER("Practitioner"), + + /** + * PractitionerRole + * + *

A specific set of Roles/Locations/specialties/services that a practitioner may perform at an organization for a + * period of time. + */ + PRACTITIONER_ROLE("PractitionerRole"), + + /** + * Procedure + * + *

An action that is or was performed on or for a patient. This can be a physical intervention like an operation, or + * less invasive like long term services, counseling, or hypnotherapy. + */ + PROCEDURE("Procedure"), + + /** + * Provenance + * + *

Provenance of a resource is a record that describes entities and processes involved in producing and delivering or + * otherwise influencing that resource. Provenance provides a critical foundation for assessing authenticity, enabling + * trust, and allowing reproducibility. Provenance assertions are a form of contextual metadata and can themselves become + * important records with their own provenance. Provenance statement indicates clinical significance in terms of + * confidence in authenticity, reliability, and trustworthiness, integrity, and stage in lifecycle (e.g. Document + * Completion - has the artifact been legally authenticated), all of which may impact security, privacy, and trust + * policies. + */ + PROVENANCE("Provenance"), + + /** + * Questionnaire + * + *

A structured set of questions intended to guide the collection of answers from end-users. Questionnaires provide + * detailed control over order, presentation, phraseology and grouping to allow coherent, consistent data collection. + */ + QUESTIONNAIRE("Questionnaire"), + + /** + * QuestionnaireResponse + * + *

A structured set of questions and their answers. The questions are ordered and grouped into coherent subsets, + * corresponding to the structure of the grouping of the questionnaire being responded to. + */ + QUESTIONNAIRE_RESPONSE("QuestionnaireResponse"), + + /** + * RegulatedAuthorization + * + *

Regulatory approval, clearance or licencing related to a regulated product, treatment, facility or activity that is + * cited in a guidance, regulation, rule or legislative act. An example is Market Authorization relating to a Medicinal + * Product. + */ + REGULATED_AUTHORIZATION("RegulatedAuthorization"), + + /** + * RelatedPerson + * + *

Information about a person that is involved in the care for a patient, but who is not the target of healthcare, nor + * has a formal responsibility in the care process. + */ + RELATED_PERSON("RelatedPerson"), + + /** + * RequestGroup + * + *

A group of related requests that can be used to capture intended activities that have inter-dependencies such as + * "give this medication after that one". + */ + REQUEST_GROUP("RequestGroup"), + + /** + * ResearchDefinition + * + *

The ResearchDefinition resource describes the conditional state (population and any exposures being compared within + * the population) and outcome (if specified) that the knowledge (evidence, assertion, recommendation) is about. + */ + RESEARCH_DEFINITION("ResearchDefinition"), + + /** + * ResearchElementDefinition + * + *

The ResearchElementDefinition resource describes a "PICO" element that knowledge (evidence, assertion, + * recommendation) is about. + */ + RESEARCH_ELEMENT_DEFINITION("ResearchElementDefinition"), + + /** + * ResearchStudy + * + *

A process where a researcher or organization plans and then executes a series of steps intended to increase the + * field of healthcare-related knowledge. This includes studies of safety, efficacy, comparative effectiveness and other + * information about medications, devices, therapies and other interventional and investigative techniques. A + * ResearchStudy involves the gathering of information about human or animal subjects. + */ + RESEARCH_STUDY("ResearchStudy"), + + /** + * ResearchSubject + * + *

A physical entity which is the primary unit of operational and/or administrative interest in a study. + */ + RESEARCH_SUBJECT("ResearchSubject"), + + /** + * RiskAssessment + * + *

An assessment of the likely outcome(s) for a patient or other subject as well as the likelihood of each outcome. + */ + RISK_ASSESSMENT("RiskAssessment"), + + /** + * Schedule + * + *

A container for slots of time that may be available for booking appointments. + */ + SCHEDULE("Schedule"), + + /** + * SearchParameter + * + *

A search parameter that defines a named search item that can be used to search/filter on a resource. + */ + SEARCH_PARAMETER("SearchParameter"), + + /** + * ServiceRequest + * + *

A record of a request for service such as diagnostic investigations, treatments, or operations to be performed. + */ + SERVICE_REQUEST("ServiceRequest"), + + /** + * Slot + * + *

A slot of time on a schedule that may be available for booking appointments. + */ + SLOT("Slot"), + + /** + * Specimen + * + *

A sample to be used for analysis. + */ + SPECIMEN("Specimen"), + + /** + * SpecimenDefinition + * + *

A kind of specimen with associated set of requirements. + */ + SPECIMEN_DEFINITION("SpecimenDefinition"), + + /** + * StructureDefinition + * + *

A definition of a FHIR structure. This resource is used to describe the underlying resources, data types defined in + * FHIR, and also for describing extensions and constraints on resources and data types. + */ + STRUCTURE_DEFINITION("StructureDefinition"), + + /** + * StructureMap + * + *

A Map of relationships between 2 structures that can be used to transform data. + */ + STRUCTURE_MAP("StructureMap"), + + /** + * Subscription + * + *

The subscription resource is used to define a push-based subscription from a server to another system. Once a + * subscription is registered with the server, the server checks every resource that is created or updated, and if the + * resource matches the given criteria, it sends a message on the defined "channel" so that another system can take an + * appropriate action. + */ + SUBSCRIPTION("Subscription"), + + /** + * SubscriptionStatus + * + *

The SubscriptionStatus resource describes the state of a Subscription during notifications. + */ + SUBSCRIPTION_STATUS("SubscriptionStatus"), + + /** + * SubscriptionTopic + * + *

Describes a stream of resource state changes identified by trigger criteria and annotated with labels useful to + * filter projections from this topic. + */ + SUBSCRIPTION_TOPIC("SubscriptionTopic"), + + /** + * Substance + * + *

A homogeneous material with a definite composition. + */ + SUBSTANCE("Substance"), + + /** + * SubstanceDefinition + * + *

The detailed description of a substance, typically at a level beyond what is used for prescribing. + */ + SUBSTANCE_DEFINITION("SubstanceDefinition"), + + /** + * SupplyDelivery + * + *

Record of delivery of what is supplied. + */ + SUPPLY_DELIVERY("SupplyDelivery"), + + /** + * SupplyRequest + * + *

A record of a request for a medication, substance or device used in the healthcare setting. + */ + SUPPLY_REQUEST("SupplyRequest"), + + /** + * Task + * + *

A task to be performed. + */ + TASK("Task"), + + /** + * TerminologyCapabilities + * + *

A TerminologyCapabilities resource documents a set of capabilities (behaviors) of a FHIR Terminology Server that + * may be used as a statement of actual server functionality or a statement of required or desired server implementation. + */ + TERMINOLOGY_CAPABILITIES("TerminologyCapabilities"), + + /** + * TestReport + * + *

A summary of information based on the results of executing a TestScript. + */ + TEST_REPORT("TestReport"), + + /** + * TestScript + * + *

A structured set of tests against a FHIR server or client implementation to determine compliance against the FHIR + * specification. + */ + TEST_SCRIPT("TestScript"), + + /** + * ValueSet + * + *

A ValueSet resource instance specifies a set of codes drawn from one or more code systems, intended for use in a + * particular context. Value sets link between [[[CodeSystem]]] definitions and their use in [coded elements] + * (terminologies.html). + */ + VALUE_SET("ValueSet"), + + /** + * VerificationResult + * + *

Describes validation requirements, source(s), status and dates for one or more elements. + */ + VERIFICATION_RESULT("VerificationResult"), + + /** + * VisionPrescription + * + *

An authorization for the provision of glasses and/or contact lenses to a patient. + */ + VISION_PRESCRIPTION("VisionPrescription"); + + private final String value; + + ResourceTypeName(String value) { + this.value = value; + } + + /** + * @return + * The String value of the resource type name + */ + public java.lang.String value() { + return value; + } +} diff --git a/fhir-persistence/src/main/java/com/ibm/fhir/persistence/util/FHIRPersistenceUtil.java b/fhir-persistence/src/main/java/com/ibm/fhir/persistence/util/FHIRPersistenceUtil.java index 69e6cd28359..16babd5eb4a 100644 --- a/fhir-persistence/src/main/java/com/ibm/fhir/persistence/util/FHIRPersistenceUtil.java +++ b/fhir-persistence/src/main/java/com/ibm/fhir/persistence/util/FHIRPersistenceUtil.java @@ -9,11 +9,18 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeParseException; +import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.logging.Logger; +import com.ibm.fhir.config.FHIRConfigHelper; +import com.ibm.fhir.config.FHIRConfiguration; import com.ibm.fhir.config.FHIRRequestContext; +import com.ibm.fhir.config.Interaction; +import com.ibm.fhir.config.PropertyGroup; +import com.ibm.fhir.config.ResourcesConfigAdapter; import com.ibm.fhir.core.HTTPReturnPreference; import com.ibm.fhir.model.resource.Resource; import com.ibm.fhir.model.resource.Resource.Builder; @@ -32,6 +39,8 @@ import com.ibm.fhir.persistence.context.impl.FHIRSystemHistoryContextImpl; import com.ibm.fhir.persistence.exception.FHIRPersistenceException; +import org.owasp.encoder.Encode; + public class FHIRPersistenceUtil { private static final Logger log = Logger.getLogger(FHIRPersistenceUtil.class.getName()); @@ -107,15 +116,27 @@ public static FHIRSystemHistoryContext parseSystemHistoryParameters(Map typesSupportingHistory = config.getSupportedResourceTypes(Interaction.HISTORY); + for (String v: values) { - String[] resourceTypes = v.split(","); + List resourceTypes = Arrays.asList(v.split("\\s*,\\s*")); for (String resourceType: resourceTypes) { - if (ModelSupport.isResourceType(resourceType)) { - context.addResourceType(resourceType); - } else { - String msg = "Invalid resource type name"; + if (!ModelSupport.isConcreteResourceType(resourceType)) { + String msg = "_type parameter has invalid resource type: " + Encode.forHtml(resourceType); throw new FHIRPersistenceException(msg) .withIssue(FHIRUtil.buildOperationOutcomeIssue(msg, IssueType.INVALID)); + } else if (!typesSupportingHistory.contains(resourceType)) { + String msg = "history interaction is not supported for _type parameter value: " + Encode.forHtml(resourceType); + throw new FHIRPersistenceException(msg) + .withIssue(FHIRUtil.buildOperationOutcomeIssue(msg, IssueType.NOT_SUPPORTED)); + } else { + context.addResourceType(resourceType); } } } diff --git a/fhir-persistence/src/main/java/com/ibm/fhir/persistence/util/PartitionedSequentialKey.java b/fhir-persistence/src/main/java/com/ibm/fhir/persistence/util/PartitionedSequentialKey.java index 4c9b498fb6c..7ffc72a6d43 100644 --- a/fhir-persistence/src/main/java/com/ibm/fhir/persistence/util/PartitionedSequentialKey.java +++ b/fhir-persistence/src/main/java/com/ibm/fhir/persistence/util/PartitionedSequentialKey.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2021 + * (C) Copyright IBM Corp. 2021, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,7 +8,6 @@ import java.time.Instant; import java.util.Objects; -import java.util.UUID; /** * A key which contains partition, tstamp and identifier values. This key can be diff --git a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/util/FHIRPersistenceUtilTest.java b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/util/FHIRPersistenceUtilTest.java index 05453b124bc..eb756a16401 100644 --- a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/util/FHIRPersistenceUtilTest.java +++ b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/util/FHIRPersistenceUtilTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2021 + * (C) Copyright IBM Corp. 2021, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -7,13 +7,26 @@ package com.ibm.fhir.persistence.util; import static com.ibm.fhir.model.type.String.string; +import static org.testng.Assert.assertTrue; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; -import org.testng.annotations.Test; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import com.ibm.fhir.config.FHIRRequestContext; +import com.ibm.fhir.exception.FHIRException; import com.ibm.fhir.model.resource.Patient; import com.ibm.fhir.model.type.HumanName; +import com.ibm.fhir.persistence.context.FHIRSystemHistoryContext; +import com.ibm.fhir.persistence.exception.FHIRPersistenceException; + +import org.testng.annotations.Test; /** * Unit tests for FHIRPersistenceUtil @@ -68,4 +81,95 @@ public void testCopyAndSetMetaId() { assertNotNull(prepared.getMeta().getLastUpdated()); assertEquals(prepared.getMeta().getLastUpdated(), lastUpdated); } + + /** + * Test the parsing of system history parameters into a HistoryContext + * with no implicit or explicit _type filter. + */ + @Test + public void testParseSystemHistoryParameters() throws FHIRException { + String originalTenantId = FHIRRequestContext.get().getTenantId(); + try { + // change to a tenant (any tenant) that has `useImplicitTypeScopingForWholeSystemInteractions = false` + FHIRRequestContext.get().setTenantId("all"); + + // no explicit _type param + Map> params = new HashMap<>(); + FHIRSystemHistoryContext historyContext = FHIRPersistenceUtil.parseSystemHistoryParameters(params, false); + + assertTrue(historyContext.getResourceTypes().isEmpty(), historyContext.getResourceTypes().toString()); + } finally { + FHIRRequestContext.get().setTenantId(originalTenantId); + } + } + + /** + * Test the parsing of system history parameters into a HistoryContext + * with an explicit _type filter. + */ + @Test + public void testParseSystemHistoryParameters_explicitTypes() throws FHIRException { + String originalTenantId = FHIRRequestContext.get().getTenantId(); + + // add a _type parameter value with a list of resource types + List explicitTypes = Arrays.asList("Patient","Device","Observation","Condition","Medication"); + Map> params = new HashMap<>(); + params.put("_type", Collections.singletonList(String.join(",",explicitTypes))); + + try { + // change to a tenant (any tenant) that has `useImplicitTypeScopingForWholeSystemInteractions = true` + FHIRRequestContext.get().setTenantId("default"); + FHIRSystemHistoryContext historyContext = FHIRPersistenceUtil.parseSystemHistoryParameters(params, false); + assertEquals(historyContext.getResourceTypes(), explicitTypes); + + // change to a tenant (any tenant) that has `useImplicitTypeScopingForWholeSystemInteractions = false` + FHIRRequestContext.get().setTenantId("all"); + historyContext = FHIRPersistenceUtil.parseSystemHistoryParameters(params, false); + assertEquals(historyContext.getResourceTypes(), explicitTypes); + } finally { + FHIRRequestContext.get().setTenantId(originalTenantId); + } + } + + @Test + public void testParseSystemHistoryParameters_multipleTypeParam() throws FHIRException { + String originalTenantId = FHIRRequestContext.get().getTenantId(); + + // add a _type parameter value with a list of resource types + List explicitTypes1 = Arrays.asList("Patient","Device","Observation","Condition","Medication"); + List explicitTypes2 = Arrays.asList("Practitioner","PractitionerRole","Organization"); + List combinedExplicitTypes = Stream.concat(explicitTypes1.stream(), explicitTypes2.stream()).collect(Collectors.toList()); + + Map> params = new HashMap<>(); + params.put("_type", Arrays.asList(String.join(",",explicitTypes1), String.join(",", explicitTypes2))); + + try { + // change to a tenant (any tenant) that has `useImplicitTypeScopingForWholeSystemInteractions = true` + FHIRRequestContext.get().setTenantId("default"); + FHIRSystemHistoryContext historyContext = FHIRPersistenceUtil.parseSystemHistoryParameters(params, false); + assertEquals(historyContext.getResourceTypes(), combinedExplicitTypes); + } finally { + FHIRRequestContext.get().setTenantId(originalTenantId); + } + } + + @Test(expectedExceptions = FHIRPersistenceException.class) + public void testParseSystemHistoryParameters_nullTypeParam() throws FHIRException { + // no explicit _type param + Map> params = new HashMap<>(); + params.put("_type", null); + FHIRSystemHistoryContext historyContext = FHIRPersistenceUtil.parseSystemHistoryParameters(params, false); + + assertTrue(historyContext.getResourceTypes().isEmpty(), historyContext.getResourceTypes().toString()); + } + + @Test(expectedExceptions = FHIRPersistenceException.class) + public void testParseSystemHistoryParameters_emptyTypeParam() throws FHIRException { + // no explicit _type param + Map> params = new HashMap<>(); + params.put("_type", Collections.emptyList()); + FHIRSystemHistoryContext historyContext = FHIRPersistenceUtil.parseSystemHistoryParameters(params, false); + + assertTrue(historyContext.getResourceTypes().isEmpty(), historyContext.getResourceTypes().toString()); + } } \ No newline at end of file diff --git a/fhir-search/src/main/java/com/ibm/fhir/search/SearchConstants.java b/fhir-search/src/main/java/com/ibm/fhir/search/SearchConstants.java index d7541e76b48..35c17fbddb4 100644 --- a/fhir-search/src/main/java/com/ibm/fhir/search/SearchConstants.java +++ b/fhir-search/src/main/java/com/ibm/fhir/search/SearchConstants.java @@ -144,7 +144,7 @@ private SearchConstants() { // set as unmodifiable public static final Set SEARCH_SINGLETON_PARAMETER_NAMES = - Collections.unmodifiableSet(new HashSet<>(Arrays.asList(SORT, COUNT, PAGE, SUMMARY, TOTAL, ELEMENTS, RESOURCE_TYPE))); + Collections.unmodifiableSet(new HashSet<>(Arrays.asList(SORT, COUNT, PAGE, SUMMARY, TOTAL, ELEMENTS))); // Set of whole-system search parameters indexed in global parameter tables public static final Set SYSTEM_LEVEL_GLOBAL_PARAMETER_NAMES = diff --git a/fhir-search/src/main/java/com/ibm/fhir/search/compartment/CompartmentCache.java b/fhir-search/src/main/java/com/ibm/fhir/search/compartment/CompartmentCache.java index f8ea19ecc2a..b5ae2e6323c 100644 --- a/fhir-search/src/main/java/com/ibm/fhir/search/compartment/CompartmentCache.java +++ b/fhir-search/src/main/java/com/ibm/fhir/search/compartment/CompartmentCache.java @@ -6,11 +6,11 @@ package com.ibm.fhir.search.compartment; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; /** @@ -21,7 +21,7 @@ public class CompartmentCache { /** * A map from the includable resourceType codes (resourceType name) to their inclusion criteria params */ - private Map> codeAndParams = new HashMap<>(); + private Map> codeAndParams = new HashMap<>(); /** * constructor @@ -40,7 +40,7 @@ public CompartmentCache() { public void add(java.lang.String inclusionResourceCode, List params) { if (params != null) { // Fast Conversion to java.lang.String - List paramsAsStrings = params.stream().map(param -> param.getValue()).collect(Collectors.toList()); + Set paramsAsStrings = params.stream().map(param -> param.getValue()).collect(Collectors.toSet()); codeAndParams.put(inclusionResourceCode, paramsAsStrings); } } @@ -50,8 +50,8 @@ public void add(java.lang.String inclusionResourceCode, List getResourceTypesInCompartment() { - return Collections.unmodifiableList(new ArrayList(codeAndParams.keySet())); + public Set getResourceTypesInCompartment() { + return Collections.unmodifiableSet(codeAndParams.keySet()); } /** @@ -60,12 +60,12 @@ public List getResourceTypesInCompartment() { * @param resourceType * @return */ - public List getParametersByResourceTypeInCompartment(String resourceType) { - List results; + public Set getParametersByResourceTypeInCompartment(String resourceType) { + Set results; if (resourceType != null && codeAndParams.containsKey(resourceType)) { - results = Collections.unmodifiableList(codeAndParams.get(resourceType)); + results = Collections.unmodifiableSet(codeAndParams.get(resourceType)); } else { - results = Collections.unmodifiableList(Collections.emptyList()); + results = Collections.emptySet(); } return results; } diff --git a/fhir-search/src/main/java/com/ibm/fhir/search/compartment/CompartmentHelper.java b/fhir-search/src/main/java/com/ibm/fhir/search/compartment/CompartmentHelper.java index f04f418d9e5..9ff70409979 100644 --- a/fhir-search/src/main/java/com/ibm/fhir/search/compartment/CompartmentHelper.java +++ b/fhir-search/src/main/java/com/ibm/fhir/search/compartment/CompartmentHelper.java @@ -66,7 +66,7 @@ public CompartmentHelper() { if (LOG.isLoggable(Level.FINER)) { for (Entry entry : compartmentMap.entrySet()) { CompartmentCache cc = entry.getValue(); - Map> paramsByResourceType = cc.getResourceTypesInCompartment().stream() + Map> paramsByResourceType = cc.getResourceTypesInCompartment().stream() .collect(Collectors.toMap(t -> t, t -> cc.getParametersByResourceTypeInCompartment(t))); LOG.finer(entry.getKey() + ": " + paramsByResourceType); } @@ -136,7 +136,7 @@ public static final void buildMaps(Map compMap, Map getCompartmentResourceTypes(final String compartment) throws FHIRSearchException { + public Set getCompartmentResourceTypes(final String compartment) throws FHIRSearchException { checkValidCompartment(compartment); return compartmentMap.get(compartment).getResourceTypesInCompartment(); } @@ -149,7 +149,7 @@ public List getCompartmentResourceTypes(final String compartment) throws * @return * @throws FHIRSearchException if the passed resourceType does not exist within the passed compartment */ - public List getCompartmentResourceTypeInclusionCriteria(final String compartment, final String resourceType) throws FHIRSearchException { + public Set getCompartmentResourceTypeInclusionCriteria(final String compartment, final String resourceType) throws FHIRSearchException { checkValidCompartmentAndResource(compartment, resourceType); return compartmentMap.get(compartment).getParametersByResourceTypeInCompartment(resourceType); } diff --git a/fhir-search/src/main/java/com/ibm/fhir/search/util/SearchHelper.java b/fhir-search/src/main/java/com/ibm/fhir/search/util/SearchHelper.java index cfc487bbf6c..2a180849cdf 100644 --- a/fhir-search/src/main/java/com/ibm/fhir/search/util/SearchHelper.java +++ b/fhir-search/src/main/java/com/ibm/fhir/search/util/SearchHelper.java @@ -30,7 +30,9 @@ import com.ibm.fhir.config.FHIRConfigHelper; import com.ibm.fhir.config.FHIRConfiguration; import com.ibm.fhir.config.FHIRRequestContext; +import com.ibm.fhir.config.Interaction; import com.ibm.fhir.config.PropertyGroup; +import com.ibm.fhir.config.ResourcesConfigAdapter; import com.ibm.fhir.config.PropertyGroup.PropertyEntry; import com.ibm.fhir.core.FHIRConstants; import com.ibm.fhir.model.resource.CodeSystem; @@ -81,6 +83,8 @@ import com.ibm.fhir.term.util.CodeSystemSupport; import com.ibm.fhir.term.util.ValueSetSupport; +import org.owasp.encoder.Encode; + /** * A helper class with methods for working with HL7 FHIR search. */ @@ -400,35 +404,32 @@ public FHIRSearchContext parseQueryParameters(Class resourceType, throw SearchExceptionUtil.buildNewInvalidSearchException("system search not supported with _include or _revinclude."); } - // Check for unsupported uses of _type + // Process the _type parameter(s) if (queryParameters.containsKey(SearchConstants.RESOURCE_TYPE)) { - if (Resource.class.equals(resourceType)) { - // Only first value is used, which matches behavior of other parameters that are supposed to be specified at most once - String resTypes = queryParameters.get(SearchConstants.RESOURCE_TYPE).get(0); - List tmpResourceTypes = Arrays.asList(resTypes.split("\\s*,\\s*")); - for (String resType : tmpResourceTypes) { - try { - if (ModelSupport.isConcreteResourceType(resType)) { - resourceTypes.add(resType); - } else { - manageException("_type search parameter has invalid resource type: " + resType, IssueType.INVALID, context, true); - continue; - } - } catch (FHIRSearchException se) { - // If we're in lenient mode and there was an issue parsing the resource type then log and move on to the next one. - if (lenient) { - String msg = "Resource type '" + resType + "' for _type search parameter ignored"; - log.log(Level.FINE, msg, se); - context.addOutcomeIssue(FHIRUtil.buildOperationOutcomeIssue(IssueSeverity.WARNING, IssueType.INVALID, msg)); + if (!Resource.class.equals(resourceType)) { + manageException("_type search parameter is only supported with system search", IssueType.NOT_SUPPORTED, context, false); + } else { + List values = queryParameters.get(SearchConstants.RESOURCE_TYPE); + + PropertyGroup pg = FHIRConfigHelper.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES); + ResourcesConfigAdapter config = new ResourcesConfigAdapter(pg); + Set searchableTypes = config.getSupportedResourceTypes(Interaction.SEARCH); + + for (String v: values) { + List tmpResourceTypes = Arrays.asList(v.split("\\s*,\\s*")); + for (String tmpResourceType: tmpResourceTypes) { + if (!ModelSupport.isConcreteResourceType(tmpResourceType)) { + String msg = "_type parameter has invalid resource type: " + Encode.forHtml(tmpResourceType); + manageException(msg, IssueType.INVALID, context, false); + } else if (!searchableTypes.contains(tmpResourceType)) { + String msg = "Search interaction is not supported for _type parameter value: " + Encode.forHtml(tmpResourceType); + manageException(msg, IssueType.NOT_SUPPORTED, context, false); } else { - throw se; + resourceTypes.add(tmpResourceType); } } } } - else { - manageException("_type search parameter is only supported with system search", IssueType.NOT_SUPPORTED, context, false); - } } queryParameters.remove(SearchConstants.RESOURCE_TYPE); diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/compartment/CompartmentCacheTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/compartment/CompartmentCacheTest.java index 89de615ab9c..3d6eacbb0f0 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/compartment/CompartmentCacheTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/compartment/CompartmentCacheTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019 + * (C) Copyright IBM Corp. 2019, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -13,6 +13,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; import org.testng.annotations.Test; @@ -29,7 +30,7 @@ public class CompartmentCacheTest extends BaseSearchTest { @Test() public void testGetResourceTypesInCompartmentWhenEmptyCompartmentCache() { CompartmentCache cache = new CompartmentCache(); - List results = cache.getResourceTypesInCompartment(); + Set results = cache.getResourceTypesInCompartment(); assertNotNull(results); assertTrue(results.isEmpty()); } @@ -39,7 +40,7 @@ public void testGetParametersByResourceTypeInCompartmentWhenEmptyCompartmentCach CompartmentCache cache = new CompartmentCache(); // Null Test - List results = cache.getParametersByResourceTypeInCompartment(null); + Set results = cache.getParametersByResourceTypeInCompartment(null); assertNotNull(results); assertTrue(results.isEmpty()); @@ -54,9 +55,9 @@ public void testGetParametersByResourceTypeInCompartmentWhenEmptyCompartmentCach public void testGetResourceTypesInCompartmentUnmodifiable() { // Confirms unmodifiable results. CompartmentCache cache = new CompartmentCache(); - List results = cache.getParametersByResourceTypeInCompartment("FudgeBrownie"); + Set results = cache.getParametersByResourceTypeInCompartment("FudgeBrownie"); assertNotNull(results); - results.clear(); + results.add("test"); } @Test(expectedExceptions = { UnsupportedOperationException.class }) @@ -72,7 +73,7 @@ public void testGetParametersByResourceTypeInCompartmentUnmodifiable() { cache4.add("FrenchFries", params); // - Should no longer be Empty - List results = cache4.getParametersByResourceTypeInCompartment("FrenchFries"); + Set results = cache4.getParametersByResourceTypeInCompartment("FrenchFries"); assertNotNull(results); results.clear(); } @@ -100,7 +101,7 @@ public void testCompartmentCacheAddOneDefinition() { assertNotNull(cache4); // - should still respond with Empty. - List results = cache4.getParametersByResourceTypeInCompartment("FrenchFries"); + Set results = cache4.getParametersByResourceTypeInCompartment("FrenchFries"); assertNotNull(results); assertTrue(results.isEmpty()); diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/compartment/CompartmentHelperTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/compartment/CompartmentHelperTest.java index 2c67f5d120c..35603809c10 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/compartment/CompartmentHelperTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/compartment/CompartmentHelperTest.java @@ -12,7 +12,6 @@ import static org.testng.Assert.assertTrue; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Set; @@ -42,7 +41,7 @@ public void testBuildCompartmentMap() { @Test public void testGetCompartmentResourceTypeExists() throws FHIRSearchException { // The Compartment Does not Exist Exists - List results = compartmentHelper.getCompartmentResourceTypes("Patient"); + Set results = compartmentHelper.getCompartmentResourceTypes("Patient"); assertNotNull(results); assertFalse(results.isEmpty()); } @@ -87,7 +86,7 @@ public void testCheckInvalidCompartmentAndResourceNotExist() throws FHIRSearchEx @Test() public void testGetCompartmentResourceTypeInclusionCriteria() throws FHIRSearchException { - List results = compartmentHelper.getCompartmentResourceTypeInclusionCriteria("Patient", "CommunicationRequest"); + Set results = compartmentHelper.getCompartmentResourceTypeInclusionCriteria("Patient", "CommunicationRequest"); assertNotNull(results); assertFalse(results.isEmpty()); } diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/test/TypeParameterParseTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/test/TypeParameterParseTest.java index 6c6fe6f8352..1bc5bbd5451 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/test/TypeParameterParseTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/test/TypeParameterParseTest.java @@ -81,28 +81,42 @@ public void testTypeNonSystemSearch_strict() throws Exception { } @Test - public void testTypeMultipleParams_lenient() throws Exception { + public void testTypeMultipleParams() throws Exception { Map> queryParameters = new HashMap<>(); queryParameters.put("_type", Arrays.asList("Patient", "Practitioner")); FHIRSearchContext context = searchHelper.parseQueryParameters(Resource.class, queryParameters, true, true); assertNotNull(context); assertNotNull(context.getSearchResourceTypes()); - assertEquals(context.getSearchResourceTypes().size(), 1); + assertEquals(context.getSearchResourceTypes().size(), 2); assertTrue(context.getSearchResourceTypes().contains("Patient")); + assertTrue(context.getSearchResourceTypes().contains("Practitioner")); } @Test - public void testTypeMultipleParams_strict() throws Exception { + public void testTypeMultipleParams_oneInvalid_lenient() throws Exception { + Map> queryParameters = new HashMap<>(); + + queryParameters.put("_type", Arrays.asList("Patient", "Bogus", "Practitioner")); + FHIRSearchContext context = searchHelper.parseQueryParameters(Resource.class, queryParameters, true, true); + assertNotNull(context); + assertNotNull(context.getSearchResourceTypes()); + assertEquals(context.getSearchResourceTypes().size(), 2); + assertTrue(context.getSearchResourceTypes().contains("Patient")); + assertTrue(context.getSearchResourceTypes().contains("Practitioner")); + } + + @Test + public void testTypeMultipleParams_oneInvalid_strict() throws Exception { Map> queryParameters = new HashMap<>(); boolean isExceptionThrown = false; - queryParameters.put("_type", Arrays.asList("Patient", "Practitioner")); + queryParameters.put("_type", Arrays.asList("Patient", "Bogus", "Practitioner")); try { searchHelper.parseQueryParameters(Resource.class, queryParameters, false, true); } catch(Exception ex) { isExceptionThrown = true; - assertEquals(ex.getMessage(), "Search parameter '_type' is specified multiple times"); + assertEquals(ex.getMessage(), "_type parameter has invalid resource type: Bogus"); } assertTrue(isExceptionThrown); @@ -130,7 +144,7 @@ public void testTypeInvalid_strict() throws Exception { searchHelper.parseQueryParameters(Resource.class, queryParameters, false, true); } catch(Exception ex) { isExceptionThrown = true; - assertEquals(ex.getMessage(), "_type search parameter has invalid resource type: invalid"); + assertEquals(ex.getMessage(), "_type parameter has invalid resource type: invalid"); } assertTrue(isExceptionThrown); @@ -156,7 +170,7 @@ public void testTypeAbstract_strict() throws Exception { searchHelper.parseQueryParameters(Resource.class, queryParameters, false, true); } catch(Exception ex) { isExceptionThrown = true; - assertEquals(ex.getMessage(), "_type search parameter has invalid resource type: Resource"); + assertEquals(ex.getMessage(), "_type parameter has invalid resource type: Resource"); } assertTrue(isExceptionThrown); diff --git a/fhir-server-spi/src/main/java/com/ibm/fhir/server/spi/operation/AbstractOperation.java b/fhir-server-spi/src/main/java/com/ibm/fhir/server/spi/operation/AbstractOperation.java index 52e40b68a26..32b5588ca75 100644 --- a/fhir-server-spi/src/main/java/com/ibm/fhir/server/spi/operation/AbstractOperation.java +++ b/fhir-server-spi/src/main/java/com/ibm/fhir/server/spi/operation/AbstractOperation.java @@ -41,10 +41,7 @@ public String getName() { } /** - * Validate the input parameters, invoke the operation, validate the output parameters, and return the result. - * - * @throws FHIROperationException - * if input or output parameters fail validation or an exception occurs + * @implNote Validates the input parameters, calls doInvoke, validates the output parameters, and returns the result. */ @Override public final Parameters invoke( @@ -83,15 +80,6 @@ protected abstract Parameters doInvoke( FHIRResourceHelpers resourceHelper, SearchHelper searchHelper) throws FHIROperationException; - protected Parameters.Parameter getParameter(Parameters parameters, String name) { - for (Parameters.Parameter parameter : parameters.getParameter()) { - if (name.equals(parameter.getName().getValue())) { - return parameter; - } - } - return null; - } - protected List getParameterDefinitions(OperationParameterUse use) { List parameterDefinitions = new ArrayList(); OperationDefinition definition = getDefinition(); @@ -103,7 +91,36 @@ protected List getParameterDefinitions(OperationP return parameterDefinitions; } + /** + * Get the first instance of a parameter by name + * + * @param parameters + * @param name non-null + * @return the first parameter that matches the given name; or null if none exist + * @throws NullPointerException if name is null + */ + protected Parameters.Parameter getParameter(Parameters parameters, String name) { + Objects.requireNonNull(name, "name"); + + for (Parameters.Parameter parameter : parameters.getParameter()) { + if (name.equals(parameter.getName().getValue())) { + return parameter; + } + } + return null; + } + + /** + * Get all instances of a parameter by name + * + * @param parameters + * @param name non-null + * @return a list of parameters that match the given name; never null + * @throws NullPointerException if name is null + */ protected List getParameters(Parameters parameters, String name) { + Objects.requireNonNull(name, "name"); + List result = new ArrayList(); if (parameters == null) { return result; @@ -238,7 +255,7 @@ protected void validateOutputParameters(FHIROperationContext operationContext, P protected void validateParameters(FHIROperationContext operationContext, Parameters parameters, OperationParameterUse use) throws FHIROperationException { String direction = OperationParameterUse.IN.equals(use) ? "input" : "output"; - // Shortcut when we want to pass something specific backup + // Shortcut when we want to pass something specific through (e.g. a non-FHIR response) if ("output".equals(direction) && operationContext.getProperty(FHIROperationContext.PROPNAME_RESPONSE) != null) { return; } diff --git a/fhir-server-spi/src/main/java/com/ibm/fhir/server/spi/operation/FHIROperation.java b/fhir-server-spi/src/main/java/com/ibm/fhir/server/spi/operation/FHIROperation.java index 9ce052098aa..885cc127d66 100644 --- a/fhir-server-spi/src/main/java/com/ibm/fhir/server/spi/operation/FHIROperation.java +++ b/fhir-server-spi/src/main/java/com/ibm/fhir/server/spi/operation/FHIROperation.java @@ -15,6 +15,12 @@ public interface FHIROperation { String getName(); + /** + * Invoke the operation. + * + * @throws FHIROperationException + * if input or output parameters fail validation or an exception occurs + */ Parameters invoke(FHIROperationContext operationContext, Class resourceType, String logicalId, String versionId, Parameters parameters, FHIRResourceHelpers resourceHelpers, SearchHelper searchHelper) throws FHIROperationException; diff --git a/fhir-server/src/test/java/com/ibm/fhir/server/test/ServerResolveFunctionTest.java b/fhir-server/src/test/java/com/ibm/fhir/server/test/ServerResolveFunctionTest.java index 6898185d8ab..a6abf24e76d 100644 --- a/fhir-server/src/test/java/com/ibm/fhir/server/test/ServerResolveFunctionTest.java +++ b/fhir-server/src/test/java/com/ibm/fhir/server/test/ServerResolveFunctionTest.java @@ -75,30 +75,6 @@ protected boolean matchesServiceBaseUrl(String baseUrl) { }); } - /** - * Helper function to replace the previously deprecated persistence layer method which has - * now been removed. Injects the meta elements into the resource before calling the - * persistence create method. - * - * @param - * @param persistence - * @param context - * @param resource - * @return - * @throws FHIRPersistenceException - */ - private SingleResourceResult create(FHIRPersistence persistence, FHIRPersistenceContext context, T resource) throws FHIRPersistenceException { - - // Generate a new logical resource id - final String logicalId = persistence.generateResourceId(); - - // Set the resource id and meta fields. - final int newVersionNumber = 1; - final Instant lastUpdated = Instant.now(ZoneOffset.UTC); - T updatedResource = FHIRPersistenceUtil.copyAndSetResourceMetaFields(resource, logicalId, newVersionNumber, lastUpdated); - return persistence.create(context, updatedResource); - } - /** * Helper function to replace the previously deprecated persistence layer method which has * now been removed. Injects the meta elements into the resource before calling the @@ -325,7 +301,6 @@ public FHIRPersistence getFHIRPersistenceImplementation(String factoryPropertyNa public static class PersistenceImpl implements FHIRPersistence { private final Map, Map>> map = new HashMap<>(); - @SuppressWarnings("unchecked") @Override public SingleResourceResult create(FHIRPersistenceContext context, T resource) throws FHIRPersistenceException { Class resourceType = resource.getClass(); @@ -388,14 +363,14 @@ public SingleResourceResult update( return createOrUpdate(resource); } - @SuppressWarnings("unchecked") @Override public MultiResourceResult history( FHIRPersistenceContext context, Class resourceType, String logicalId) throws FHIRPersistenceException { - List versions = map.getOrDefault(resourceType, Collections.emptyMap()).getOrDefault(logicalId, Collections.emptyList()); + List versions = map.getOrDefault(resourceType, Collections.emptyMap()) + .getOrDefault(logicalId, Collections.emptyList()); // Convert the resource list to a results list List> resourceResults = new ArrayList<>(versions.size()); diff --git a/fhir-smart/src/main/java/com/ibm/fhir/smart/AuthzPolicyEnforcementPersistenceInterceptor.java b/fhir-smart/src/main/java/com/ibm/fhir/smart/AuthzPolicyEnforcementPersistenceInterceptor.java index 94d5ae36d4f..0f3f0ef6b60 100644 --- a/fhir-smart/src/main/java/com/ibm/fhir/smart/AuthzPolicyEnforcementPersistenceInterceptor.java +++ b/fhir-smart/src/main/java/com/ibm/fhir/smart/AuthzPolicyEnforcementPersistenceInterceptor.java @@ -7,9 +7,9 @@ package com.ibm.fhir.smart; import java.io.StringReader; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -25,7 +25,6 @@ import com.ibm.fhir.config.FHIRConfigHelper; import com.ibm.fhir.config.FHIRRequestContext; -import com.ibm.fhir.exception.FHIRException; import com.ibm.fhir.model.resource.Bundle; import com.ibm.fhir.model.resource.Parameters; import com.ibm.fhir.model.resource.Parameters.Parameter; @@ -90,7 +89,7 @@ public class AuthzPolicyEnforcementPersistenceInterceptor implements FHIRPersist @Override public void beforeInvoke(FHIROperationContext context) throws FHIRPersistenceInterceptorException { Permission neededPermission; - List resourceTypes; + Set resourceTypes; if ("import".equals(context.getOperationCode())) { neededPermission = Permission.WRITE; resourceTypes = computeImportResourceTypes(context); @@ -164,7 +163,7 @@ public void afterInvoke(FHIROperationContext context) throws FHIRPersistenceInte } } - private List computeImportResourceTypes(FHIROperationContext context) { + private Set computeImportResourceTypes(FHIROperationContext context) { Parameters parameters = (Parameters) context.getProperty(FHIROperationContext.PROPNAME_REQUEST_PARAMETERS); Set types = parameters.getParameter().stream() .filter(p -> "input".equals(p.getName().getValue())) @@ -174,39 +173,20 @@ private List computeImportResourceTypes(FHIROperationContext context) { .map(pp -> pp.getValue().as(ModelSupport.FHIR_STRING).getValue())) .collect(Collectors.toSet()); - return new ArrayList<>(types); + return Collections.unmodifiableSet(types); } - /** - * gets the supported resource types - * @return - */ - private List getSupportedResourceTypes() { - try { - List rts = FHIRConfigHelper.getSupportedResourceTypes(); - if (!rts.isEmpty()) { - return rts; - } - } catch (FHIRException e) { - log.throwing(this.getClass().getName(), "getSupportedResourceTypes", e); - } - return ModelSupport.getResourceTypes(false) - .stream() - .map(clz -> clz.getSimpleName()) - .collect(Collectors.toList()); - } - - private List computeExportResourceTypes(FHIROperationContext context) { - List supportedResourceTypes = getSupportedResourceTypes(); - List resourceTypes = new ArrayList<>(); + private Set computeExportResourceTypes(FHIROperationContext context) { + Set supportedResourceTypes = FHIRConfigHelper.getSupportedResourceTypes(); + Set resourceTypes = new HashSet<>(); Parameters parameters = (Parameters) context.getProperty(FHIROperationContext.PROPNAME_REQUEST_PARAMETERS); Optional typesParam = parameters.getParameter().stream().filter(p -> "_type".equals(p.getName().getValue())).findFirst(); switch (context.getType()) { case INSTANCE: // Group/:id/$export case RESOURCE_TYPE: // Patient/$export - // Either way, the set resourceTypes to export are those from the Patient compartment + // Either way, the set of resourceTypes to export are those from the Patient compartment try { - List compartmentResourceMembers = compartmentHelper.getCompartmentResourceTypes(PATIENT); + Set compartmentResourceMembers = compartmentHelper.getCompartmentResourceTypes(PATIENT); if (typesParam.isPresent() && typesParam.get().getValue() != null) { String typesString = typesParam.get().getValue().as(ModelSupport.FHIR_STRING).getValue(); for (String requestedType : Arrays.asList(typesString.split(","))) { @@ -229,7 +209,7 @@ private List computeExportResourceTypes(FHIROperationContext context) { case SYSTEM: if (typesParam.isPresent() && typesParam.get().getValue() != null) { String typesString = typesParam.get().getValue().as(ModelSupport.FHIR_STRING).getValue(); - resourceTypes = Arrays.asList(typesString.split(",")); + resourceTypes = Set.of(typesString.split(",")); for (String resourceType : resourceTypes) { if (!supportedResourceTypes.contains(resourceType)) { throw new IllegalStateException("Requested resource is not configured"); @@ -831,7 +811,7 @@ private boolean isInCompartment(Resource resource, CompartmentType compartmentTy return true; } - List inclusionCriteria = compartmentHelper.getCompartmentResourceTypeInclusionCriteria(compartment, resourceType); + Set inclusionCriteria = compartmentHelper.getCompartmentResourceTypeInclusionCriteria(compartment, resourceType); EvaluationContext resourceContext = new FHIRPathEvaluator.EvaluationContext(resource); diff --git a/fhir-swagger-generator/src/main/java/com/ibm/fhir/openapi/generator/FHIROpenApiGenerator.java b/fhir-swagger-generator/src/main/java/com/ibm/fhir/openapi/generator/FHIROpenApiGenerator.java index c61d7da5ece..311f5ffcb82 100644 --- a/fhir-swagger-generator/src/main/java/com/ibm/fhir/openapi/generator/FHIROpenApiGenerator.java +++ b/fhir-swagger-generator/src/main/java/com/ibm/fhir/openapi/generator/FHIROpenApiGenerator.java @@ -472,7 +472,7 @@ private static void generateOneFilePerCompartment(Filter filter) throws Exceptio if (DomainResource.class.isAssignableFrom(compartmentModelClass) && DomainResource.class != compartmentModelClass) { - List resourceClassNames = getCompartmentClassNames(compartmentClassName); + Set resourceClassNames = getCompartmentClassNames(compartmentClassName); if (resourceClassNames == null || resourceClassNames.isEmpty()) { continue; } @@ -2385,11 +2385,11 @@ public static List getClassNames() { return classNamesList; } - private static List getCompartmentClassNames(String compartment) { + private static Set getCompartmentClassNames(String compartment) { try { return compartmentHelper.getCompartmentResourceTypes(compartment); } catch (FHIRSearchException e) { - return Collections.emptyList(); + return Collections.emptySet(); } } diff --git a/fhir-swagger-generator/src/main/java/com/ibm/fhir/swagger/generator/FHIRSwaggerGenerator.java b/fhir-swagger-generator/src/main/java/com/ibm/fhir/swagger/generator/FHIRSwaggerGenerator.java index 445292c879c..6831139a5ed 100644 --- a/fhir-swagger-generator/src/main/java/com/ibm/fhir/swagger/generator/FHIRSwaggerGenerator.java +++ b/fhir-swagger-generator/src/main/java/com/ibm/fhir/swagger/generator/FHIRSwaggerGenerator.java @@ -220,7 +220,7 @@ public static void main(String[] args) throws Exception { private static void generateCompartmentSwagger(Class compartmentModelClass, Filter filter) throws Exception, ClassNotFoundException, Error { String compartmentClassName = compartmentModelClass.getSimpleName(); - List resourceClassNames = getCompartmentClassNames(compartmentClassName); + Set resourceClassNames = getCompartmentClassNames(compartmentClassName); if (resourceClassNames == null || resourceClassNames.isEmpty()) { return; } @@ -1951,11 +1951,11 @@ private static ElementDefinition getElementDefinition(StructureDefinition struct throw new RuntimeException("Unable to retrieve element definition for " + elementName + " in " + modelClass.getName()); } - private static List getCompartmentClassNames(String compartment) { + private static Set getCompartmentClassNames(String compartment) { try { return compartmentHelper.getCompartmentResourceTypes(compartment); } catch (FHIRSearchException e) { - return Collections.emptyList(); + return Collections.emptySet(); } } diff --git a/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/ExportOperation.java b/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/ExportOperation.java index ee7a36ae2f1..034a0f39bf7 100644 --- a/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/ExportOperation.java +++ b/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/ExportOperation.java @@ -7,6 +7,7 @@ package com.ibm.fhir.operation.bulkdata; import java.util.List; +import java.util.Set; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; @@ -70,7 +71,7 @@ protected Parameters doInvoke(FHIROperationContext operationContext, Class types = export.checkAndValidateTypes(exportType, parameters, queryParameters); + Set types = export.checkAndValidateTypes(exportType, parameters, queryParameters); if (!ExportType.INVALID.equals(exportType)) { diff --git a/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/client/BulkDataClient.java b/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/client/BulkDataClient.java index c1800081099..86db2e2b7df 100644 --- a/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/client/BulkDataClient.java +++ b/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/client/BulkDataClient.java @@ -20,9 +20,9 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; import javax.crypto.KeyGenerator; import javax.ws.rs.core.Response; @@ -41,14 +41,12 @@ import org.apache.http.util.EntityUtils; import com.ibm.fhir.config.FHIRRequestContext; -import com.ibm.fhir.core.FHIRMediaType; import com.ibm.fhir.exception.FHIROperationException; import com.ibm.fhir.model.generator.exception.FHIRGeneratorException; import com.ibm.fhir.model.resource.OperationOutcome; import com.ibm.fhir.model.type.Instant; import com.ibm.fhir.model.type.code.IssueType; import com.ibm.fhir.model.util.FHIRUtil; -import com.ibm.fhir.model.util.ModelSupport; import com.ibm.fhir.operation.bulkdata.OperationConstants; import com.ibm.fhir.operation.bulkdata.OperationConstants.ExportType; import com.ibm.fhir.operation.bulkdata.client.action.batch.BatchCancelRequestAction; @@ -65,7 +63,6 @@ import com.ibm.fhir.operation.bulkdata.model.type.StorageType; import com.ibm.fhir.operation.bulkdata.model.url.DownloadUrl; import com.ibm.fhir.operation.bulkdata.util.BulkDataExportUtil; -import com.ibm.fhir.search.compartment.CompartmentHelper; /** * BulkData Client to connect to the other server. @@ -127,7 +124,6 @@ public class BulkDataClient { private String incomingUrl = null; private String baseUri = null; private ConfigurationAdapter adapter = null; - private CompartmentHelper compartmentHelper = null; /** * @@ -144,11 +140,11 @@ public BulkDataClient(String bulkdataSource, String outcomeSource, String incomi this.incomingUrl = incomingUrl; this.baseUri = baseUri; this.adapter = adapter; - this.compartmentHelper = new CompartmentHelper(); } /** - * + * Submit the export job. + * * @param since * @param types * @param exportType @@ -158,8 +154,8 @@ public BulkDataClient(String bulkdataSource, String outcomeSource, String incomi * @return * @throws Exception */ - public String submitExport(Instant since, List types, ExportType exportType, String outputFormat, String typeFilters, String groupId) - throws Exception { + public String submitExport(Instant since, Set types, ExportType exportType, String outputFormat, String typeFilters, String groupId) + throws Exception { JobInstanceRequest.Builder builder = JobInstanceRequest.builder(); builder.applicationName(adapter.getApplicationName()); builder.moduleName(adapter.getModuleName()); @@ -179,31 +175,19 @@ public String submitExport(Instant since, List types, ExportType exportT builder.cosBucketPathPrefix(getRandomPrefix()); builder.fhirExportFormat(outputFormat); - // Export Type - FHIR - types = types.stream().filter(t -> !t.isEmpty()).collect(Collectors.toList()); - String resourceType = String.join(",", types); switch (exportType) { case PATIENT: builder.jobXMLName(JobType.EXPORT_PATIENT.value()); - if (resourceType == null || resourceType.isEmpty() ) { - resourceType = String.join(",", compartmentHelper.getCompartmentResourceTypes("Patient")); - } break; case GROUP: builder.jobXMLName(JobType.EXPORT_GROUP.value()); - builder.fhirPatientGroupId(groupId); - if (resourceType == null || resourceType.isEmpty()) { - resourceType = String.join(",", compartmentHelper.getCompartmentResourceTypes("Patient")); - } break; default: // We have two implementations for system export, but the "fast" version // does not support typeFilters. We also allow the configuration to // force use of the legacy implementation for those who don't like the change - if (typeFilters != null || !adapter.isFastExport() - || FHIRMediaType.APPLICATION_PARQUET.equals(outputFormat) || StorageType.FILE.equals(adapter.getStorageProviderStorageType(source))) { // Use the legacy implementation builder.jobXMLName(JobType.EXPORT.value()); @@ -211,16 +195,10 @@ public String submitExport(Instant since, List types, ExportType exportT // No typeFilter, so we use the fast export which bypasses FHIR search builder.jobXMLName(JobType.EXPORT_FAST.value()); } - if (resourceType == null || resourceType.isEmpty()) { - resourceType = ModelSupport.getResourceTypes() - .stream() - .map(r -> r.getSimpleName()) - .collect(Collectors.joining(",")); - } break; } - builder.fhirResourceType(resourceType); + builder.fhirResourceType(String.join(",", types)); /* * There used to be an else path here where since is null, then set builder.fhirSearchFromDate("1970-01-01T00:00:00Z");. diff --git a/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/processor/ExportImportBulkData.java b/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/processor/ExportImportBulkData.java index af9b70eb494..552cbdf5b49 100644 --- a/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/processor/ExportImportBulkData.java +++ b/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/processor/ExportImportBulkData.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019, 2021 + * (C) Copyright IBM Corp. 2019, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -7,6 +7,7 @@ package com.ibm.fhir.operation.bulkdata.processor; import java.util.List; +import java.util.Set; import javax.ws.rs.core.MediaType; @@ -43,7 +44,7 @@ public interface ExportImportBulkData { * @throws FHIROperationException */ public Parameters export(String logicalId, OperationConstants.ExportType exportType, MediaType outputFormat, - Instant since, List types, List typeFilters, FHIROperationContext operationContext) throws FHIROperationException; + Instant since, Set types, List typeFilters, FHIROperationContext operationContext) throws FHIROperationException; /** * Pattern: POST [Base]/$import diff --git a/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/processor/impl/ExportImportImpl.java b/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/processor/impl/ExportImportImpl.java index 9460c8bd701..4a0bf2e9d1a 100644 --- a/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/processor/impl/ExportImportImpl.java +++ b/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/processor/impl/ExportImportImpl.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019, 2021 + * (C) Copyright IBM Corp. 2019, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -7,6 +7,7 @@ package com.ibm.fhir.operation.bulkdata.processor.impl; import java.util.List; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -53,8 +54,8 @@ public ExportImportImpl(String bulkdataSource, String outcomeSource, String base } @Override - public Parameters export(String logicalId, OperationConstants.ExportType exportType, MediaType outputFormat, Instant since, List types, - List typeFilters, FHIROperationContext operationContext) throws FHIROperationException { + public Parameters export(String logicalId, OperationConstants.ExportType exportType, MediaType outputFormat, Instant since, + Set types, List typeFilters, FHIROperationContext operationContext) throws FHIROperationException { try { // Check if this is a group export String groupId = null; diff --git a/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/util/BulkDataExportUtil.java b/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/util/BulkDataExportUtil.java index cea930cc1c5..39ac02e1491 100644 --- a/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/util/BulkDataExportUtil.java +++ b/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/util/BulkDataExportUtil.java @@ -7,6 +7,7 @@ package com.ibm.fhir.operation.bulkdata.util; import static com.ibm.fhir.model.type.String.string; +import static com.ibm.fhir.model.util.ModelSupport.FHIR_STRING; import java.util.ArrayList; import java.util.HashSet; @@ -14,15 +15,12 @@ import java.util.Optional; import java.util.Set; import java.util.logging.Logger; -import java.util.stream.Collectors; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import com.ibm.fhir.config.FHIRConfigHelper; -import com.ibm.fhir.config.FHIRConfiguration; import com.ibm.fhir.core.FHIRMediaType; -import com.ibm.fhir.exception.FHIRException; import com.ibm.fhir.exception.FHIROperationException; import com.ibm.fhir.model.resource.OperationOutcome.Issue; import com.ibm.fhir.model.resource.Parameters; @@ -31,25 +29,21 @@ import com.ibm.fhir.model.type.Instant; import com.ibm.fhir.model.type.code.IssueSeverity; import com.ibm.fhir.model.type.code.IssueType; -import com.ibm.fhir.model.util.ModelSupport; import com.ibm.fhir.operation.bulkdata.OperationConstants; import com.ibm.fhir.operation.bulkdata.OperationConstants.ExportType; import com.ibm.fhir.operation.bulkdata.model.PollingLocationResponse; import com.ibm.fhir.operation.bulkdata.model.transformer.JobIdEncodingTransformer; import com.ibm.fhir.search.compartment.CompartmentHelper; -import com.ibm.fhir.search.exception.FHIRSearchException; import com.ibm.fhir.server.spi.operation.FHIROperationContext; +import org.owasp.encoder.Encode; + /** * BulkData Util captures common methods */ public class BulkDataExportUtil { private static final Logger LOG = Logger.getLogger(BulkDataExportUtil.class.getName()); - private static Set RESOURCE_TYPES = ModelSupport.getResourceTypes(false).stream() - .map(Class::getSimpleName) - .collect(Collectors.toSet()); - private final CompartmentHelper compartmentHelper; public BulkDataExportUtil() { @@ -77,52 +71,6 @@ public OperationConstants.ExportType checkExportType(FHIROperationContext.Type t return exportType; } - /** - * - * @implNote originally this was in the PartitionMapper for Patient Export. - * - * @param resourceTypes - * @throws FHIROperationException - */ - public void checkExportPatientResourceTypes(Set resourceTypes) throws FHIROperationException { - boolean valid = false; - try { - // Also Check to see if the Export is valid for the Compartment. - List allCompartmentResourceTypes = compartmentHelper.getCompartmentResourceTypes("Patient"); - Set supportedResourceTypes = getSupportedResourceTypes(); - List tmp = resourceTypes.stream() - .filter(item -> allCompartmentResourceTypes.contains(item) && supportedResourceTypes.contains(item)) - .collect(Collectors.toList()); - valid = tmp != null && !tmp.isEmpty(); - } catch (Exception e) { - // No Operation intentionally - } - if (!valid) { - throw buildOperationException("Resource Type outside of the Patient Compartment in Export", IssueType.INVALID); - } - } - - /** - * gets the supported resource types - * @return - */ - public Set getSupportedResourceTypes() { - try { - if (!FHIRConfigHelper.getBooleanProperty("fhirServer/resources/" + FHIRConfiguration.PROPERTY_FIELD_RESOURCES_OPEN, Boolean.TRUE)) { - List rts = FHIRConfigHelper.getSupportedResourceTypes(); - System.out.println("RTS -> " + rts); - if (rts != null && !rts.isEmpty()) { - rts.remove("Resource"); - rts.remove("DomainResource"); - return new HashSet<>(rts); - } - } - } catch (FHIRException e) { - LOG.throwing(BulkDataExportUtil.class.getName(), "getSupportedResourceTypes", e); - } - return RESOURCE_TYPES; - } - public MediaType checkAndConvertToMediaType(Parameters parameters) throws FHIROperationException { /* * The format for the requested bulk data files to be generated as per [FHIR Asynchronous Request @@ -219,7 +167,8 @@ public Instant checkAndExtractSince(Parameters parameters) { * @return * @throws FHIROperationException */ - public List checkAndValidateTypes(OperationConstants.ExportType exportType, Parameters parameters, MultivaluedMap queryParameters) throws FHIROperationException { + public Set checkAndValidateTypes(OperationConstants.ExportType exportType, Parameters parameters, + MultivaluedMap queryParameters) throws FHIROperationException { /* * Only resources of the specified resource types(s) SHALL be included in the response. If this parameter is * omitted, the server SHALL return all supported resources within the scope of the client authorization. For @@ -233,26 +182,26 @@ public List checkAndValidateTypes(OperationConstants.ExportType exportTy * within the file set. For example _type=Practitioner could be used to bulk data extract all Practitioner * resources from a FHIR endpoint. */ - Set supportedResourceTypes = getSupportedResourceTypes(); + Set supportedResourceTypes = FHIRConfigHelper.getSupportedResourceTypes(); Set result = new HashSet<>(); if (parameters != null) { for (Parameters.Parameter parameter : parameters.getParameter()) { // The model makes sure getName is never non-null. if (OperationConstants.PARAM_TYPE.equals(parameter.getName().getValue())) { - if (parameter.getValue() != null) { - String types = - parameter.getValue().as(com.ibm.fhir.model.type.String.class).getValue(); - for (String type : types.split(",")) { - // Type will never be null here. - if (!type.isEmpty() && supportedResourceTypes.contains(type)) { - result.add(type); - } else { - throw buildOperationException("invalid resource type sent as a parameter to $export operation", IssueType.INVALID); - } - } - } else { + if (parameter.getValue() == null) { throw buildOperationException("invalid resource type sent as a parameter to $export operation", IssueType.INVALID); } + + String types = parameter.getValue().as(FHIR_STRING).getValue(); + for (String type : types.split(",")) { + // Type will never be null here. + if (!type.isEmpty() && supportedResourceTypes.contains(type)) { + result.add(type); + } else { + throw buildOperationException("invalid resource type sent as a parameter to $export operation: " + + Encode.forHtml(type), IssueType.INVALID); + } + } } } } @@ -266,9 +215,8 @@ public List checkAndValidateTypes(OperationConstants.ExportType exportTy if (!type.isEmpty() && supportedResourceTypes.contains(type)) { result.add(type); } else { - throw buildOperationException( - "invalid resource type sent as a parameter to $export operation", - IssueType.INVALID); + throw buildOperationException("invalid resource type sent as a parameter to $export operation: " + + Encode.forHtml(type), IssueType.INVALID); } } } @@ -280,33 +228,58 @@ public List checkAndValidateTypes(OperationConstants.ExportType exportTy result = new HashSet<>(supportedResourceTypes); } else if (ExportType.PATIENT.equals(exportType) || ExportType.GROUP.equals(exportType)) { if (!result.isEmpty()) { - checkExportPatientResourceTypes(result); + result = filterTypesToPatientResourceTypes(result); + } else { + result = getDefaultsForPatientCompartment(supportedResourceTypes); + } + } + return result; + } + + /** + * Filter the passed resourceTypes down to the set that can be in the Patient compartment. + * + * @param resourceTypes + * @return a new Set that represents the subset of the requested types that can be exported in a Patient export + * @throws FHIROperationException if none of the passed resourceTypes are valid for a Patient export + * @implNote originally this was in the PartitionMapper for Patient Export. + */ + private Set filterTypesToPatientResourceTypes(Set resourceTypes) throws FHIROperationException { + Set result = new HashSet<>(); + + // Filter the set of types down to those that exist in the Patient compartment. + Set patientCompartmentResourceTypes = new HashSet<>(compartmentHelper.getCompartmentResourceTypes("Patient")); + for (String resourceType : resourceTypes) { + if (patientCompartmentResourceTypes.contains(resourceType)) { + result.add(resourceType); } else { - return addDefaultsForPatientCompartment(); + LOG.info("Requested type '" + Encode.forHtml(resourceType) + "' cannot be in the Patient compartment;" + + "this is not supported for Patient/Group export and so this type will be skipped"); } } - return new ArrayList<>(result); + + if (result.isEmpty()) { + throw buildOperationException("None of the requested types are valid for a Patient (or Group) export", IssueType.INVALID); + } + return result; } /** - * gets the defaults for the patient compartment (filtered based on supported). + * Get the set of default resource types for a Patient export (filtered by what is supported for the tenant in the FHIRRequestContext). + * + * @param supportedResourceTypes * @return * @throws FHIROperationException */ - public List addDefaultsForPatientCompartment() throws FHIROperationException { - try { - // Ensures only supported resources are added - Set supportedResourceTypes = getSupportedResourceTypes(); - List supportedDefaultResourceTypes = new ArrayList<>(); - for (String compartmentResourceType : compartmentHelper.getCompartmentResourceTypes("Patient")) { - if (supportedResourceTypes.contains(compartmentResourceType)) { - supportedDefaultResourceTypes.add(compartmentResourceType); - } + private Set getDefaultsForPatientCompartment(Set supportedResourceTypes) throws FHIROperationException { + // Ensures only supported resources are added + Set supportedDefaultResourceTypes = new HashSet<>(); + for (String compartmentResourceType : compartmentHelper.getCompartmentResourceTypes("Patient")) { + if (supportedResourceTypes.contains(compartmentResourceType)) { + supportedDefaultResourceTypes.add(compartmentResourceType); } - return supportedDefaultResourceTypes; - } catch (FHIRSearchException e) { - throw buildOperationException("unable to process the Patient compartment into types", IssueType.UNKNOWN); } + return supportedDefaultResourceTypes; } public List checkAndValidateTypeFilters(Parameters parameters) throws FHIROperationException { @@ -325,9 +298,8 @@ public List checkAndValidateTypeFilters(Parameters parameters) throws FH if (parameters != null) { for (Parameters.Parameter parameter : parameters.getParameter()) { if (OperationConstants.PARAM_TYPE_FILTER.equals(parameter.getName().getValue())) { - if (parameter.getValue() != null && parameter.getValue().is(com.ibm.fhir.model.type.String.class)) { - String typeFilters = - parameter.getValue().as(com.ibm.fhir.model.type.String.class).getValue(); + if (parameter.getValue() != null && parameter.getValue().is(FHIR_STRING)) { + String typeFilters = parameter.getValue().as(FHIR_STRING).getValue(); for (String typeFilter : typeFilters.split(",")) { // Type will never be null here, just check for blanks diff --git a/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/util/BulkDataImportUtil.java b/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/util/BulkDataImportUtil.java index 4b504bb1ab1..ea4448f7e99 100644 --- a/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/util/BulkDataImportUtil.java +++ b/operation/fhir-operation-bulkdata/src/main/java/com/ibm/fhir/operation/bulkdata/util/BulkDataImportUtil.java @@ -13,9 +13,9 @@ import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; +import java.util.Set; import com.ibm.fhir.config.FHIRConfigHelper; -import com.ibm.fhir.exception.FHIRException; import com.ibm.fhir.exception.FHIROperationException; import com.ibm.fhir.model.resource.Parameters; import com.ibm.fhir.model.type.code.IssueType; @@ -115,7 +115,7 @@ public List retrieveInputs() throws FHIROperationException { List inputs = new ArrayList<>(); try { - List supportedResourceTypes = FHIRConfigHelper.getSupportedResourceTypes(); + Set supportedResourceTypes = FHIRConfigHelper.getSupportedResourceTypes(); Collection result = evaluator.evaluate(evaluationContext, "parameter.where(name = 'input')"); Iterator iter = result.iterator(); @@ -158,8 +158,6 @@ public List retrieveInputs() throws FHIROperationException { throw buildExceptionWithIssue("$import invalid parameters with expression in 'input'", e, IssueType.INVALID); } catch (FHIROperationException opEx) { throw opEx; - } catch (FHIRException e) { - throw buildExceptionWithIssue("Unable to retrieve server configuration", e, IssueType.INVALID); } if (inputs.isEmpty()) { diff --git a/operation/fhir-operation-bulkdata/src/test/java/com/ibm/fhir/operation/bulkdata/util/BulkDataExportUtilTest.java b/operation/fhir-operation-bulkdata/src/test/java/com/ibm/fhir/operation/bulkdata/util/BulkDataExportUtilTest.java index 16fb9a63400..09d29e2e5f1 100644 --- a/operation/fhir-operation-bulkdata/src/test/java/com/ibm/fhir/operation/bulkdata/util/BulkDataExportUtilTest.java +++ b/operation/fhir-operation-bulkdata/src/test/java/com/ibm/fhir/operation/bulkdata/util/BulkDataExportUtilTest.java @@ -21,6 +21,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import javax.ws.rs.core.MediaType; @@ -363,7 +364,7 @@ public void testCheckAndValidateTypesPatientWithoutComma() throws FHIROperationE Parameters ps = builder.build(); BulkDataExportUtil util = new BulkDataExportUtil(); - List types = util.checkAndValidateTypes(ExportType.SYSTEM, ps, null); + Set types = util.checkAndValidateTypes(ExportType.SYSTEM, ps, null); assertNotNull(types); } @@ -378,7 +379,7 @@ public void testCheckAndValidateTypesPatientWithComma() throws FHIROperationExce builder.parameter(parameters); Parameters ps = builder.build(); - List types = util.checkAndValidateTypes(ExportType.SYSTEM, ps, null); + Set types = util.checkAndValidateTypes(ExportType.SYSTEM, ps, null); assertNotNull(types); } @@ -393,7 +394,7 @@ public void testCheckAndValidateTypesPatientMedicationWithComma() throws FHIROpe Parameters ps = builder.build(); BulkDataExportUtil util = new BulkDataExportUtil(); - List types = util.checkAndValidateTypes(ExportType.SYSTEM, ps, null); + Set types = util.checkAndValidateTypes(ExportType.SYSTEM, ps, null); assertNotNull(types); } @@ -423,7 +424,7 @@ public void testCheckAndValidateTypesWithExtraComma() throws FHIROperationExcept Parameters ps = builder.build(); BulkDataExportUtil util = new BulkDataExportUtil(); - List result = util.checkAndValidateTypes(ExportType.SYSTEM, ps, null); + Set result = util.checkAndValidateTypes(ExportType.SYSTEM, ps, null); assertNotNull(result); assertFalse(result.isEmpty()); } @@ -439,7 +440,7 @@ public void testCheckAndValidateTypesNoParameters() throws FHIROperationExceptio builder.parameter(parameters); Parameters ps = builder.build(); - List result = util.checkAndValidateTypes(ExportType.SYSTEM, ps, null); + Set result = util.checkAndValidateTypes(ExportType.SYSTEM, ps, null); assertNotNull(result); assertFalse(result.isEmpty()); } @@ -447,7 +448,7 @@ public void testCheckAndValidateTypesNoParameters() throws FHIROperationExceptio @Test public void testCheckAndValidateTypesEmptyParameters() throws FHIROperationException { BulkDataExportUtil util = new BulkDataExportUtil(); - List result = util.checkAndValidateTypes(ExportType.SYSTEM, null, null); + Set result = util.checkAndValidateTypes(ExportType.SYSTEM, null, null); assertNotNull(result); assertFalse(result.isEmpty()); } diff --git a/operation/fhir-operation-everything/src/main/java/com/ibm/fhir/operation/everything/EverythingOperation.java b/operation/fhir-operation-everything/src/main/java/com/ibm/fhir/operation/everything/EverythingOperation.java index 52966afd1be..1ce626343f2 100644 --- a/operation/fhir-operation-everything/src/main/java/com/ibm/fhir/operation/everything/EverythingOperation.java +++ b/operation/fhir-operation-everything/src/main/java/com/ibm/fhir/operation/everything/EverythingOperation.java @@ -24,7 +24,9 @@ import com.ibm.fhir.config.FHIRConfigHelper; import com.ibm.fhir.config.FHIRConfiguration; import com.ibm.fhir.config.FHIRRequestContext; +import com.ibm.fhir.config.Interaction; import com.ibm.fhir.config.PropertyGroup; +import com.ibm.fhir.config.ResourcesConfigAdapter; import com.ibm.fhir.core.FHIRConstants; import com.ibm.fhir.core.HTTPHandlingPreference; import com.ibm.fhir.exception.FHIROperationException; @@ -399,18 +401,10 @@ private List getDefaultIncludedResourceTypes(FHIRResourceHelpers resourc List resourceTypes = new ArrayList<>(compartmentHelper.getCompartmentResourceTypes(PATIENT)); try { - List supportedResourceTypes = FHIRConfigHelper.getSupportedResourceTypes(); - // Examine the resource types to see if they support SEARCH - for (String resourceType: supportedResourceTypes) { - try { - resourceHelper.validateInteraction(FHIRResourceHelpers.Interaction.SEARCH, resourceType); - } catch (FHIROperationException e) { - if (LOG.isLoggable(Level.FINE)) { - LOG.fine("Removing resourceType " + resourceType + " because it does not support SEARCH"); - } - supportedResourceTypes.remove(resourceType); - } - } + PropertyGroup resourcesGroup = FHIRConfigHelper.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES); + ResourcesConfigAdapter configAdapter = new ResourcesConfigAdapter(resourcesGroup); + Set supportedResourceTypes = configAdapter.getSupportedResourceTypes(Interaction.SEARCH); + if (LOG.isLoggable(Level.FINE)) { StringBuilder resourceTypeBuilder = new StringBuilder(supportedResourceTypes.size()); resourceTypeBuilder.append("supportedResourceTypes are: "); @@ -421,12 +415,10 @@ private List getDefaultIncludedResourceTypes(FHIRResourceHelpers resourc LOG.fine(resourceTypeBuilder.toString()); } - // Need to have this if check to support server config files that do not specify resources - if (!supportedResourceTypes.isEmpty()) { - resourceTypes.retainAll(supportedResourceTypes); - } + resourceTypes.retainAll(supportedResourceTypes); } catch (Exception e) { - FHIRSearchException exceptionWithIssue = new FHIRSearchException("There has been an error retrieving the list of supported resource types of the $everything operation.", e); + FHIRSearchException exceptionWithIssue = new FHIRSearchException("There has been an error retrieving the list " + + "of supported resource types of the $everything operation.", e); LOG.throwing(this.getClass().getName(), "doInvoke", exceptionWithIssue); throw exceptionWithIssue; }