Skip to content

Commit

Permalink
Issue #1961 - Optionally reject references without resource type
Browse files Browse the repository at this point in the history
Signed-off-by: Troy Biesterfeld <tbieste@us.ibm.com>
  • Loading branch information
tbieste committed Apr 9, 2021
1 parent dc4cf55 commit 29a4cf5
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 72 deletions.
5 changes: 4 additions & 1 deletion docs/src/pages/guides/FHIRServerUsersGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ layout: post
title: IBM FHIR Server User's Guide
description: IBM FHIR Server User's Guide
Copyright: years 2017, 2021
lastupdated: "2021-03-10"
lastupdated: "2021-04-08"
permalink: /FHIRServerUsersGuide/
---

Expand Down Expand Up @@ -1948,6 +1948,7 @@ This section contains reference information about each of the configuration prop
|`fhirServer/core/defaultHandling`|string|The default handling preference of the server (*strict* or *lenient*) which determines how the server handles unrecognized search parameters and resource elements.|
|`fhirServer/core/allowClientHandlingPref`|boolean|Indicates whether the client is allowed to override the server default handling preference using the `Prefer:handling` header value part.|
|`fhirServer/core/checkReferenceTypes`|boolean|Indicates whether reference type checking is performed by the server during parsing / deserialization.|
|`fhirServer/core/requireReferenceResourceType`|boolean|Indicates whether requiring references to include a resource type is performed by the server during parsing / deserialization.|
|`fhirServer/core/serverRegistryResourceProviderEnabled`|boolean|Indicates whether the server registry resource provider should be used by the FHIR registry component to access definitional resources through the persistence layer.|
|`fhirServer/core/conditionalDeleteMaxNumber`|integer|The max number of matches supported in conditional delete. |
|`fhirServer/core/capabilityStatementCacheTimeout`|integer|The number of minutes that a tenant's CapabilityStatement is cached for the metadata endpoint. |
Expand Down Expand Up @@ -2076,6 +2077,7 @@ This section contains reference information about each of the configuration prop
|`fhirServer/core/defaultHandling`|strict|
|`fhirServer/core/allowClientHandlingPref`|true|
|`fhirServer/core/checkReferenceTypes`|true|
|`fhirServer/core/requireReferenceResourceType`|false|
|`fhirServer/core/serverRegistryResourceProviderEnabled`|false|
|`fhirServer/core/conditionalDeleteMaxNumber`|10|
|`fhirServer/core/capabilityStatementCacheTimeout`|60|
Expand Down Expand Up @@ -2187,6 +2189,7 @@ must restart the server for that change to take effect.
|`fhirServer/core/defaultHandling`|Y|Y|
|`fhirServer/core/allowClientHandlingPref`|Y|Y|
|`fhirServer/core/checkReferenceTypes`|N|N|
|`fhirServer/core/requireReferenceResourceType`|N|N|
|`fhirServer/core/serverRegistryResourceProviderEnabled`|N|N|
|`fhirServer/core/conditionalDeleteMaxNumber`|Y|Y|
|`fhirServer/core/capabilityStatementCacheTimeout`|Y|Y|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class FHIRConfiguration {
public static final String PROPERTY_DEFAULT_HANDLING = "fhirServer/core/defaultHandling";
public static final String PROPERTY_ALLOW_CLIENT_HANDLING_PREF = "fhirServer/core/allowClientHandlingPref";
public static final String PROPERTY_CHECK_REFERENCE_TYPES = "fhirServer/core/checkReferenceTypes";
public static final String PROPERTY_REQUIRE_REFERENCE_RESOURCE_TYPE = "fhirServer/core/requireReferenceResourceType";
public static final String PROPERTY_CONDITIONAL_DELETE_MAX_NUMBER = "fhirServer/core/conditionalDeleteMaxNumber";
public static final String PROPERTY_SERVER_REGISTRY_RESOURCE_PROVIDER_ENABLED = "fhirServer/core/serverRegistryResourceProviderEnabled";
public static final String PROPERTY_CAPABILITY_STATEMENT_CACHE = "fhirServer/core/capabilityStatementCacheTimeout";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* (C) Copyright IBM Corp. 2019
* (C) Copyright IBM Corp. 2019, 2021
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand Down Expand Up @@ -33,12 +33,17 @@ public final class FHIRModelConfig {
* Used to determine whether the toString method return value should be formatted
*/
public static final String PROPERTY_TO_STRING_PRETTY_PRINTING = "com.ibm.fhir.model.toStringPrettyPrinting";

/**
* Used to determine whether reference types are checked during object construction
*/
public static final String PROPERTY_CHECK_REFERENCE_TYPES = "com.ibm.fhir.model.checkReferenceTypes";

/**
* Used to determine whether reference values require resource type during object construction
*/
public static final String PROPERTY_REQUIRE_REFERENCE_RESOURCE_TYPE = "com.ibm.fhir.model.requireReferenceResourceType";

/**
* Used to determine:
* 1. whether CodeableConcepts that don't contain both system and code are checked during object construction
Expand All @@ -50,84 +55,93 @@ public final class FHIRModelConfig {
private static final int DEFAULT_TO_STRING_INDENT_AMOUNT = 2;
private static final boolean DEFAULT_TO_STRING_PRETTY_PRINTING = true;
private static final boolean DEFAULT_CHECK_REFERENCE_TYPES = true;
private static final boolean DEFAULT_REQUIRE_REFERENCE_RESOURCE_TYPE = false;
private static final boolean DEFAULT_EXTENDED_CODEABLE_CONCEPT_VALIDATION = true;

private static final Map<String, Object> properties = new ConcurrentHashMap<>();

private FHIRModelConfig() { }

public static void setToStringFormat(Format format) {
setProperty(PROPERTY_TO_STRING_FORMAT, format);
}

public static Format getToStringFormat() {
return getPropertyOrDefault(PROPERTY_TO_STRING_FORMAT, DEFAULT_TO_STRING_FORMAT, Format.class);
}

public static void setToStringIndentAmount(int indentAmount) {
setProperty(PROPERTY_TO_STRING_INDENT_AMOUNT, indentAmount);
}

public static int getToStringIndentAmount() {
return getPropertyOrDefault(PROPERTY_TO_STRING_INDENT_AMOUNT, DEFAULT_TO_STRING_INDENT_AMOUNT, Integer.class);
}

public static void setToStringPrettyPrinting(boolean prettyPrinting) {
setProperty(PROPERTY_TO_STRING_PRETTY_PRINTING, prettyPrinting);
}

public static boolean getToStringPrettyPrinting() {
return getPropertyOrDefault(PROPERTY_TO_STRING_PRETTY_PRINTING, DEFAULT_TO_STRING_PRETTY_PRINTING, Boolean.class);
}

public static void setCheckReferenceTypes(boolean checkReferenceTypes) {
setProperty(PROPERTY_CHECK_REFERENCE_TYPES, checkReferenceTypes);
}

public static boolean getCheckReferenceTypes() {
return getPropertyOrDefault(PROPERTY_CHECK_REFERENCE_TYPES, DEFAULT_CHECK_REFERENCE_TYPES, Boolean.class);
}


public static void setRequireReferenceResourceType(boolean requireReferenceResourceType) {
setProperty(PROPERTY_REQUIRE_REFERENCE_RESOURCE_TYPE, requireReferenceResourceType);
}

public static boolean getRequireReferenceResourceType() {
return getPropertyOrDefault(PROPERTY_REQUIRE_REFERENCE_RESOURCE_TYPE, DEFAULT_REQUIRE_REFERENCE_RESOURCE_TYPE, Boolean.class);
}

public static void setExtendedCodeableConceptValidation(boolean extendedCodeableConceptValidation) {
setProperty(PROPERTY_EXTENDED_CODEABLE_CONCEPT_VALIDATION, extendedCodeableConceptValidation);
}

public static boolean getExtendedCodeableConceptValidation() {
return getPropertyOrDefault(PROPERTY_EXTENDED_CODEABLE_CONCEPT_VALIDATION, DEFAULT_EXTENDED_CODEABLE_CONCEPT_VALIDATION, Boolean.class);
}

public static void setProperty(String name, Object value) {
properties.put(requireNonNull(name), requireNonNull(value));
}

public static Object removeProperty(String name) {
return properties.remove(requireNonNull(name));
}

public static <T> T removeProperty(String name, Class<T> type) {
return requireNonNull(type).cast(removeProperty(name));
}

public static Object getProperty(String name) {
return properties.get(requireNonNull(name));
}

public static Object getPropertyOrDefault(String name, Object defaultValue) {
return properties.getOrDefault(requireNonNull(name), requireNonNull(defaultValue));
}

public static <T> T getProperty(String name, Class<T> type) {
return requireNonNull(type).cast(getProperty(name));
}

public static <T> T getPropertyOrDefault(String name, T defaultValue, Class<T> type) {
return requireNonNull(type).cast(getPropertyOrDefault(name, defaultValue));
}

public static Map<String, Object> getProperties() {
return Collections.unmodifiableMap(properties);
}

public static Set<String> getPropertyNames() {
return Collections.unmodifiableSet(properties.keySet());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* (C) Copyright IBM Corp. 2019, 2020
* (C) Copyright IBM Corp. 2019, 2021
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand Down Expand Up @@ -672,54 +672,61 @@ public static void checkReferenceType(Element choiceElement, String elementName,
}

/**
* Checks that the reference contains valid resource type values.
* @param reference the reference
* @param elementName the element name
* @param referenceTypes the valid resource types for the reference
* @throws IllegalStateException if the resource type found in the reference value does not match the specified Reference.type value
* or is not one of the allowed reference types for that element
*/
public static void checkReferenceType(Reference reference, String elementName, String... referenceTypes) {
if (reference == null) {
return;
}
boolean requireReferenceResourceType = FHIRModelConfig.getRequireReferenceResourceType();
boolean checkReferenceTypes = FHIRModelConfig.getCheckReferenceTypes();
if (reference != null && checkReferenceTypes) {
String referenceType = getReferenceType(reference);
if (referenceType != null && !ModelSupport.isResourceType(referenceType)) {
throw new IllegalStateException(
String.format("Resource type found in Reference.type: '%s' for element: '%s' must be a valid resource type name",
referenceType, elementName));

String resourceType = null;
String referenceReference = getReferenceReference(reference);
List<String> referenceTypeList = Arrays.asList(referenceTypes);

if (referenceReference != null && !referenceReference.startsWith("#")) {
Matcher matcher = REFERENCE_PATTERN.matcher(referenceReference);
if (matcher.matches()) {
resourceType = matcher.group(RESOURCE_TYPE_GROUP);
}
List<String> referenceTypeList = Arrays.asList(referenceTypes);

// If there is an explicit Reference.type, ensure its an allowed type
if (referenceType != null && !referenceTypeList.contains(referenceType)) {
throw new IllegalStateException(
String.format("Resource type found in Reference.type: '%s' for element: '%s' must be one of: %s",
referenceType, elementName, referenceTypeList.toString()));
}

String referenceReference = getReferenceReference(reference);
String resourceType = null;

if (referenceReference != null && !referenceReference.startsWith("#")) {
Matcher matcher = REFERENCE_PATTERN.matcher(referenceReference);
if (matcher.matches()) {
resourceType = matcher.group(RESOURCE_TYPE_GROUP);
// If there is an explicit Reference.type, check that the resourceType pattern matches it
if (referenceType != null && !resourceType.equals(referenceType)) {
throw new IllegalStateException(
String.format("Resource type found in reference value: '%s' for element: '%s' does not match Reference.type: %s",
referenceReference, elementName, referenceType));
}
if (requireReferenceResourceType) {
// resourceType is required in the reference value
if (resourceType == null) {
throw new IllegalStateException(String.format("Resource type not found in reference value: '%s' for element: '%s'", referenceReference, elementName));
}
}
}

if (resourceType == null) {
resourceType = referenceType;
if (checkReferenceTypes) {
if (resourceType != null && !ModelSupport.isResourceType(resourceType)) {
throw new IllegalStateException(String.format("Resource type found in reference value: '%s' for element: '%s' must be a valid resource type name", referenceReference, elementName));
}

// If we've successfully inferred a type, check that its an allowed value
if (resourceType != null) {
if (!referenceTypeList.contains(resourceType)) {
throw new IllegalStateException(
String.format("Resource type found in reference value: '%s' for element: '%s' must be one of: %s",
referenceReference, elementName, referenceTypeList.toString()));
}
// If there is a resourceType in the reference value, check that it's an allowed value
if (resourceType != null && !referenceTypeList.contains(resourceType)) {
throw new IllegalStateException(String.format("Resource type found in reference value: '%s' for element: '%s' must be one of: %s", referenceReference, elementName, referenceTypeList.toString()));
}

String referenceType = getReferenceType(reference);
if (referenceType != null && !ModelSupport.isResourceType(referenceType)) {
throw new IllegalStateException(String.format("Resource type found in Reference.type: '%s' for element: '%s' must be a valid resource type name", referenceType, elementName));
}

// If there is an explicit Reference.type, ensure it's an allowed type
if (referenceType != null && !referenceTypeList.contains(referenceType)) {
throw new IllegalStateException(String.format("Resource type found in Reference.type: '%s' for element: '%s' must be one of: %s", referenceType, elementName, referenceTypeList.toString()));
}

// If there is an explicit Reference.type, check that the resourceType pattern matches it
if (referenceType != null && resourceType != null && !resourceType.equals(referenceType)) {
throw new IllegalStateException(String.format("Resource type found in reference value: '%s' for element: '%s' does not match Reference.type: %s", referenceReference, elementName, referenceType));
}
}
}
Expand Down
Loading

0 comments on commit 29a4cf5

Please sign in to comment.