merge master

This commit is contained in:
Ken Stevens 2024-11-29 12:56:58 -05:00
commit 2f8bc68e17
333 changed files with 8484 additions and 2922 deletions

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -713,6 +713,8 @@ public class FhirContext {
"org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport";
String commonCodeSystemsSupportType =
"org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService";
String snapshotGeneratingType =
"org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport";
if (ReflectionUtil.typeExists(inMemoryTermSvcType)) {
IValidationSupport inMemoryTermSvc = ReflectionUtil.newInstanceOrReturnNull(
inMemoryTermSvcType,
@ -724,11 +726,23 @@ public class FhirContext {
IValidationSupport.class,
new Class<?>[] {FhirContext.class},
new Object[] {this});
IValidationSupport snapshotGeneratingSupport = null;
if (getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
snapshotGeneratingSupport = ReflectionUtil.newInstanceOrReturnNull(
snapshotGeneratingType,
IValidationSupport.class,
new Class<?>[] {FhirContext.class},
new Object[] {this});
}
retVal = ReflectionUtil.newInstanceOrReturnNull(
"org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain",
IValidationSupport.class,
new Class<?>[] {IValidationSupport[].class},
new Object[] {new IValidationSupport[] {retVal, inMemoryTermSvc, commonCodeSystemsSupport}});
new Object[] {
new IValidationSupport[] {
retVal, inMemoryTermSvc, commonCodeSystemsSupport, snapshotGeneratingSupport
}
});
assert retVal != null
: "Failed to instantiate "
+ "org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain";
@ -1293,7 +1307,15 @@ public class FhirContext {
* @since 5.1.0
*/
public static FhirContext forCached(FhirVersionEnum theFhirVersionEnum) {
return ourStaticContexts.computeIfAbsent(theFhirVersionEnum, v -> new FhirContext(v));
return ourStaticContexts.computeIfAbsent(theFhirVersionEnum, FhirContext::forVersion);
}
/**
* An uncached version of forCached()
* @return a new FhirContext for theFhirVersionEnum
*/
public static FhirContext forVersion(FhirVersionEnum theFhirVersionEnum) {
return new FhirContext(theFhirVersionEnum);
}
private static Collection<Class<? extends IBaseResource>> toCollection(

View File

@ -135,15 +135,19 @@ public enum FhirVersionEnum {
/**
* Creates a new FhirContext for this FHIR version
* @deprecated since 7.7. Use {@link FhirContext#forVersion(FhirVersionEnum)} instead
*/
@Deprecated(forRemoval = true, since = "7.7")
public FhirContext newContext() {
return new FhirContext(this);
return FhirContext.forVersion(this);
}
/**
* Creates a new FhirContext for this FHIR version, or returns a previously created one if one exists. This
* method uses {@link FhirContext#forCached(FhirVersionEnum)} to return a cached instance.
* @deprecated since 7.7. Use {@link FhirContext#forCached(FhirVersionEnum)} instead
*/
@Deprecated(forRemoval = true, since = "7.7")
public FhirContext newContextCached() {
return FhirContext.forCached(this);
}

View File

@ -22,11 +22,26 @@ package ca.uhn.fhir.context.support;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.util.Objects;
public class ConceptValidationOptions {
private boolean myValidateDisplay;
private boolean myInferSystem;
@Override
public boolean equals(Object theO) {
if (this == theO) return true;
if (!(theO instanceof ConceptValidationOptions)) return false;
ConceptValidationOptions that = (ConceptValidationOptions) theO;
return myValidateDisplay == that.myValidateDisplay && myInferSystem == that.myInferSystem;
}
@Override
public int hashCode() {
return Objects.hash(myValidateDisplay, myInferSystem);
}
public boolean isInferSystem() {
return myInferSystem;
}

View File

@ -23,7 +23,9 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.util.ILockable;
import ca.uhn.fhir.util.ReflectionUtil;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
@ -48,6 +50,13 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
private static final Map<FhirVersionEnum, IValidationSupport> ourImplementations =
Collections.synchronizedMap(new HashMap<>());
/**
* Userdata key indicating the source package ID for this package
*/
public static final String SOURCE_PACKAGE_ID =
DefaultProfileValidationSupport.class.getName() + "_SOURCE_PACKAGE_ID";
private final FhirContext myCtx;
/**
* This module just delegates all calls to a concrete implementation which will
@ -62,7 +71,8 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
*
* @param theFhirContext The context to use
*/
public DefaultProfileValidationSupport(FhirContext theFhirContext) {
public DefaultProfileValidationSupport(@Nonnull FhirContext theFhirContext) {
Validate.notNull(theFhirContext, "FhirContext must not be null");
myCtx = theFhirContext;
IValidationSupport strategy;
@ -106,33 +116,45 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
@Override
public List<IBaseResource> fetchAllConformanceResources() {
return myDelegate.fetchAllConformanceResources();
List<IBaseResource> retVal = myDelegate.fetchAllConformanceResources();
addPackageInformation(retVal);
return retVal;
}
@Override
public <T extends IBaseResource> List<T> fetchAllStructureDefinitions() {
return myDelegate.fetchAllStructureDefinitions();
List<T> retVal = myDelegate.fetchAllStructureDefinitions();
addPackageInformation(retVal);
return retVal;
}
@Nullable
@Override
public <T extends IBaseResource> List<T> fetchAllNonBaseStructureDefinitions() {
return myDelegate.fetchAllNonBaseStructureDefinitions();
List<T> retVal = myDelegate.fetchAllNonBaseStructureDefinitions();
addPackageInformation(retVal);
return retVal;
}
@Override
public IBaseResource fetchCodeSystem(String theSystem) {
return myDelegate.fetchCodeSystem(theSystem);
IBaseResource retVal = myDelegate.fetchCodeSystem(theSystem);
addPackageInformation(retVal);
return retVal;
}
@Override
public IBaseResource fetchStructureDefinition(String theUrl) {
return myDelegate.fetchStructureDefinition(theUrl);
IBaseResource retVal = myDelegate.fetchStructureDefinition(theUrl);
addPackageInformation(retVal);
return retVal;
}
@Override
public IBaseResource fetchValueSet(String theUrl) {
return myDelegate.fetchValueSet(theUrl);
IBaseResource retVal = myDelegate.fetchValueSet(theUrl);
addPackageInformation(retVal);
return retVal;
}
public void flush() {
@ -158,4 +180,43 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
}
return urlValueString;
}
private <T extends IBaseResource> void addPackageInformation(List<T> theResources) {
if (theResources != null) {
theResources.forEach(this::addPackageInformation);
}
}
private void addPackageInformation(IBaseResource theResource) {
if (theResource != null) {
String sourcePackageId = null;
switch (myCtx.getVersion().getVersion()) {
case DSTU2:
case DSTU2_HL7ORG:
sourcePackageId = "hl7.fhir.r2.core";
break;
case DSTU2_1:
return;
case DSTU3:
sourcePackageId = "hl7.fhir.r3.core";
break;
case R4:
sourcePackageId = "hl7.fhir.r4.core";
break;
case R4B:
sourcePackageId = "hl7.fhir.r4b.core";
break;
case R5:
sourcePackageId = "hl7.fhir.r5.core";
break;
}
Validate.notNull(
sourcePackageId,
"Don't know how to handle package ID: %s",
myCtx.getVersion().getVersion());
theResource.setUserData(SOURCE_PACKAGE_ID, sourcePackageId);
}
}
}

View File

@ -41,6 +41,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -116,7 +117,8 @@ public interface IValidationSupport {
@Nonnull String theValueSetUrlToExpand)
throws ResourceNotFoundException {
Validate.notBlank(theValueSetUrlToExpand, "theValueSetUrlToExpand must not be null or blank");
IBaseResource valueSet = fetchValueSet(theValueSetUrlToExpand);
IBaseResource valueSet =
theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrlToExpand);
if (valueSet == null) {
throw new ResourceNotFoundException(
Msg.code(2024) + "Unknown ValueSet: " + UrlUtil.escapeUrlParam(theValueSetUrlToExpand));
@ -212,8 +214,8 @@ public interface IValidationSupport {
() -> fetchStructureDefinition(theUri), () -> fetchValueSet(theUri), () -> fetchCodeSystem(theUri)
};
return (T) Arrays.stream(sources)
.map(t -> t.get())
.filter(t -> t != null)
.map(Supplier::get)
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
}
@ -440,74 +442,259 @@ public interface IValidationSupport {
return "Unknown " + getFhirContext().getVersion().getVersion() + " Validation Support";
}
/**
* Defines codes in system <a href="http://hl7.org/fhir/issue-severity">http://hl7.org/fhir/issue-severity</a>.
*/
/* this enum would not be needed if we design/refactor to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult */
enum IssueSeverity {
/**
* The issue caused the action to fail, and no further checking could be performed.
*/
FATAL,
FATAL("fatal"),
/**
* The issue is sufficiently important to cause the action to fail.
*/
ERROR,
ERROR("error"),
/**
* The issue is not important enough to cause the action to fail, but may cause it to be performed suboptimally or in a way that is not as desired.
*/
WARNING,
WARNING("warning"),
/**
* The issue has no relation to the degree of success of the action.
*/
INFORMATION
INFORMATION("information"),
/**
* The operation was successful.
*/
SUCCESS("success");
// the spec for OperationOutcome mentions that a code from http://hl7.org/fhir/issue-severity is required
private final String myCode;
IssueSeverity(String theCode) {
myCode = theCode;
}
/**
* Provide mapping to a code in system <a href="http://hl7.org/fhir/issue-severity">http://hl7.org/fhir/issue-severity</a>.
* @return the code
*/
public String getCode() {
return myCode;
}
/**
* Creates a {@link IssueSeverity} object from the given code.
* @return the {@link IssueSeverity}
*/
public static IssueSeverity fromCode(String theCode) {
switch (theCode) {
case "fatal":
return FATAL;
case "error":
return ERROR;
case "warning":
return WARNING;
case "information":
return INFORMATION;
case "success":
return SUCCESS;
default:
return null;
}
}
}
enum CodeValidationIssueCode {
NOT_FOUND,
CODE_INVALID,
INVALID,
OTHER
}
/**
* Defines codes in system <a href="http://hl7.org/fhir/issue-type">http://hl7.org/fhir/issue-type</a>.
* The binding is enforced as a part of validation logic in the FHIR Core Validation library where an exception is thrown.
* Only a sub-set of these codes are defined as constants because they relate to validation,
* If there are additional ones that come up, for Remote Terminology they are currently supported via
* {@link IValidationSupport.CodeValidationIssue#CodeValidationIssue(String, IssueSeverity, String)}
* while for internal validators, more constants can be added to make things easier and consistent.
* This maps to resource OperationOutcome.issue.code.
*/
/* this enum would not be needed if we design/refactor to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult */
class CodeValidationIssueCode {
public static final CodeValidationIssueCode NOT_FOUND = new CodeValidationIssueCode("not-found");
public static final CodeValidationIssueCode CODE_INVALID = new CodeValidationIssueCode("code-invalid");
public static final CodeValidationIssueCode INVALID = new CodeValidationIssueCode("invalid");
enum CodeValidationIssueCoding {
VS_INVALID,
NOT_FOUND,
NOT_IN_VS,
private final String myCode;
INVALID_CODE,
INVALID_DISPLAY,
OTHER
}
class CodeValidationIssue {
private final String myMessage;
private final IssueSeverity mySeverity;
private final CodeValidationIssueCode myCode;
private final CodeValidationIssueCoding myCoding;
public CodeValidationIssue(
String theMessage,
IssueSeverity mySeverity,
CodeValidationIssueCode theCode,
CodeValidationIssueCoding theCoding) {
this.myMessage = theMessage;
this.mySeverity = mySeverity;
this.myCode = theCode;
this.myCoding = theCoding;
// this is intentionally not exposed
CodeValidationIssueCode(String theCode) {
myCode = theCode;
}
/**
* Retrieve the corresponding code from system <a href="http://hl7.org/fhir/issue-type">http://hl7.org/fhir/issue-type</a>.
* @return the code
*/
public String getCode() {
return myCode;
}
}
/**
* Holds information about the details of a {@link CodeValidationIssue}.
* This maps to resource OperationOutcome.issue.details.
*/
/* this enum would not be needed if we design/refactor to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult */
class CodeValidationIssueDetails {
private final String myText;
private List<CodeValidationIssueCoding> myCodings;
public CodeValidationIssueDetails(String theText) {
myText = theText;
}
// intentionally not exposed
void addCoding(CodeValidationIssueCoding theCoding) {
getCodings().add(theCoding);
}
public CodeValidationIssueDetails addCoding(String theSystem, String theCode) {
if (myCodings == null) {
myCodings = new ArrayList<>();
}
myCodings.add(new CodeValidationIssueCoding(theSystem, theCode));
return this;
}
public String getText() {
return myText;
}
public List<CodeValidationIssueCoding> getCodings() {
if (myCodings == null) {
myCodings = new ArrayList<>();
}
return myCodings;
}
}
/**
* Defines codes that can be part of the details of an issue.
* There are some constants available (pre-defined) for codes for system <a href="http://hl7.org/fhir/tools/CodeSystem/tx-issue-type">http://hl7.org/fhir/tools/CodeSystem/tx-issue-type</a>.
* This maps to resource OperationOutcome.issue.details.coding[0].code.
*/
class CodeValidationIssueCoding {
public static String TX_ISSUE_SYSTEM = "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type";
public static CodeValidationIssueCoding VS_INVALID =
new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "vs-invalid");
public static final CodeValidationIssueCoding NOT_FOUND =
new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "not-found");
public static final CodeValidationIssueCoding NOT_IN_VS =
new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "not-in-vs");
public static final CodeValidationIssueCoding INVALID_CODE =
new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "invalid-code");
public static final CodeValidationIssueCoding INVALID_DISPLAY =
new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "vs-display");
private final String mySystem, myCode;
// this is intentionally not exposed
CodeValidationIssueCoding(String theSystem, String theCode) {
mySystem = theSystem;
myCode = theCode;
}
/**
* Retrieve the corresponding code for the details of a validation issue.
* @return the code
*/
public String getCode() {
return myCode;
}
/**
* Retrieve the system for the details of a validation issue.
* @return the system
*/
public String getSystem() {
return mySystem;
}
}
/**
* This is a hapi-fhir internal version agnostic object holding information about a validation issue.
* An alternative (which requires significant refactoring) would be to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult instead.
*/
class CodeValidationIssue {
private final String myDiagnostics;
private final IssueSeverity mySeverity;
private final CodeValidationIssueCode myCode;
private CodeValidationIssueDetails myDetails;
public CodeValidationIssue(
String theDiagnostics, IssueSeverity theSeverity, CodeValidationIssueCode theTypeCode) {
this(theDiagnostics, theSeverity, theTypeCode, null);
}
public CodeValidationIssue(String theDiagnostics, IssueSeverity theSeverity, String theTypeCode) {
this(theDiagnostics, theSeverity, new CodeValidationIssueCode(theTypeCode), null);
}
public CodeValidationIssue(
String theDiagnostics,
IssueSeverity theSeverity,
CodeValidationIssueCode theType,
CodeValidationIssueCoding theDetailsCoding) {
myDiagnostics = theDiagnostics;
mySeverity = theSeverity;
myCode = theType;
// reuse the diagnostics message as a detail text message
myDetails = new CodeValidationIssueDetails(theDiagnostics);
myDetails.addCoding(theDetailsCoding);
}
/**
* @deprecated Please use {@link #getDiagnostics()} instead.
*/
@Deprecated(since = "7.4.6")
public String getMessage() {
return myMessage;
return getDiagnostics();
}
public String getDiagnostics() {
return myDiagnostics;
}
public IssueSeverity getSeverity() {
return mySeverity;
}
/**
* @deprecated Please use {@link #getType()} instead.
*/
@Deprecated(since = "7.4.6")
public CodeValidationIssueCode getCode() {
return getType();
}
public CodeValidationIssueCode getType() {
return myCode;
}
/**
* @deprecated Please use {@link #getDetails()} instead. That has support for multiple codings.
*/
@Deprecated(since = "7.4.6")
public CodeValidationIssueCoding getCoding() {
return myCoding;
return myDetails != null
? myDetails.getCodings().stream().findFirst().orElse(null)
: null;
}
public void setDetails(CodeValidationIssueDetails theDetails) {
this.myDetails = theDetails;
}
public CodeValidationIssueDetails getDetails() {
return myDetails;
}
public boolean hasIssueDetailCode(@Nonnull String theCode) {
// this method is system agnostic at the moment but it can be restricted if needed
return myDetails.getCodings().stream().anyMatch(coding -> theCode.equals(coding.getCode()));
}
}
@ -607,6 +794,7 @@ public interface IValidationSupport {
return myValue;
}
@Override
public String getType() {
return TYPE_STRING;
}
@ -641,6 +829,7 @@ public interface IValidationSupport {
return myDisplay;
}
@Override
public String getType() {
return TYPE_CODING;
}
@ -671,6 +860,10 @@ public interface IValidationSupport {
}
}
/**
* This is a hapi-fhir internal version agnostic object holding information about the validation result.
* An alternative (which requires significant refactoring) would be to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult.
*/
class CodeValidationResult {
public static final String SOURCE_DETAILS = "sourceDetails";
public static final String RESULT = "result";
@ -686,7 +879,7 @@ public interface IValidationSupport {
private String myDisplay;
private String mySourceDetails;
private List<CodeValidationIssue> myCodeValidationIssues;
private List<CodeValidationIssue> myIssues;
public CodeValidationResult() {
super();
@ -771,20 +964,45 @@ public interface IValidationSupport {
return this;
}
/**
* @deprecated Please use method {@link #getIssues()} instead.
*/
@Deprecated(since = "7.4.6")
public List<CodeValidationIssue> getCodeValidationIssues() {
if (myCodeValidationIssues == null) {
myCodeValidationIssues = new ArrayList<>();
}
return myCodeValidationIssues;
return getIssues();
}
/**
* @deprecated Please use method {@link #setIssues(List)} instead.
*/
@Deprecated(since = "7.4.6")
public CodeValidationResult setCodeValidationIssues(List<CodeValidationIssue> theCodeValidationIssues) {
myCodeValidationIssues = new ArrayList<>(theCodeValidationIssues);
return setIssues(theCodeValidationIssues);
}
/**
* @deprecated Please use method {@link #addIssue(CodeValidationIssue)} instead.
*/
@Deprecated(since = "7.4.6")
public CodeValidationResult addCodeValidationIssue(CodeValidationIssue theCodeValidationIssue) {
getCodeValidationIssues().add(theCodeValidationIssue);
return this;
}
public CodeValidationResult addCodeValidationIssue(CodeValidationIssue theCodeValidationIssue) {
getCodeValidationIssues().add(theCodeValidationIssue);
public List<CodeValidationIssue> getIssues() {
if (myIssues == null) {
myIssues = new ArrayList<>();
}
return myIssues;
}
public CodeValidationResult setIssues(List<CodeValidationIssue> theIssues) {
myIssues = new ArrayList<>(theIssues);
return this;
}
public CodeValidationResult addIssue(CodeValidationIssue theCodeValidationIssue) {
getIssues().add(theCodeValidationIssue);
return this;
}
@ -811,17 +1029,19 @@ public interface IValidationSupport {
public String getSeverityCode() {
String retVal = null;
if (getSeverity() != null) {
retVal = getSeverity().name().toLowerCase();
retVal = getSeverity().getCode();
}
return retVal;
}
/**
* Sets an issue severity as a string code. Value must be the name of
* one of the enum values in {@link IssueSeverity}. Value is case-insensitive.
* Sets an issue severity using a severity code. Please use method {@link #setSeverity(IssueSeverity)} instead.
* @param theSeverityCode the code
* @return the current {@link CodeValidationResult} instance
*/
public CodeValidationResult setSeverityCode(@Nonnull String theIssueSeverity) {
setSeverity(IssueSeverity.valueOf(theIssueSeverity.toUpperCase()));
@Deprecated(since = "7.4.6")
public CodeValidationResult setSeverityCode(@Nonnull String theSeverityCode) {
setSeverity(IssueSeverity.fromCode(theSeverityCode));
return this;
}
@ -838,6 +1058,11 @@ public interface IValidationSupport {
if (isNotBlank(getSourceDetails())) {
ParametersUtil.addParameterToParametersString(theContext, retVal, SOURCE_DETAILS, getSourceDetails());
}
/*
should translate issues as well, except that is version specific code, so it requires more refactoring
or replace the current class with org.hl7.fhir.r5.terminologies.utilities.ValidationResult
@see VersionSpecificWorkerContextWrapper#getIssuesForCodeValidation
*/
return retVal;
}
@ -1210,10 +1435,9 @@ public interface IValidationSupport {
}
/**
* <p
* Warning: This method's behaviour and naming is preserved for backwards compatibility, BUT the actual naming and
* function are not aligned.
* </p
* When validating a CodeableConcept containing multiple codings, this method can be used to control whether
* the validator requires all codings in the CodeableConcept to be valid in order to consider the
* CodeableConcept valid.
* <p>
* See VersionSpecificWorkerContextWrapper#validateCode in hapi-fhir-validation, and the refer to the values below
* for the behaviour associated with each value.
@ -1228,7 +1452,7 @@ public interface IValidationSupport {
* </p>
* @return true or false depending on the desired coding validation behaviour.
*/
default boolean isEnabledValidationForCodingsLogicalAnd() {
default boolean isCodeableConceptValidationSuccessfulIfNotAllCodingsAreValid() {
return false;
}
}

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.context.support;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
/**
* Represents parameters which can be passed to the $lookup operation for codes.
@ -72,4 +73,20 @@ public class LookupCodeRequest {
}
return myPropertyNames;
}
@Override
public boolean equals(Object theO) {
if (this == theO) return true;
if (!(theO instanceof LookupCodeRequest)) return false;
LookupCodeRequest that = (LookupCodeRequest) theO;
return Objects.equals(mySystem, that.mySystem)
&& Objects.equals(myCode, that.myCode)
&& Objects.equals(myDisplayLanguage, that.myDisplayLanguage)
&& Objects.equals(myPropertyNames, that.myPropertyNames);
}
@Override
public int hashCode() {
return Objects.hash(mySystem, myCode, myDisplayLanguage, myPropertyNames);
}
}

View File

@ -42,7 +42,7 @@ public class ValidationSupportContext {
return myCurrentlyGeneratingSnapshots;
}
public boolean isEnabledValidationForCodingsLogicalAnd() {
return myRootValidationSupport.isEnabledValidationForCodingsLogicalAnd();
public boolean isCodeableConceptValidationSuccessfulIfNotAllCodingsAreValid() {
return myRootValidationSupport.isCodeableConceptValidationSuccessfulIfNotAllCodingsAreValid();
}
}

View File

@ -21,6 +21,8 @@ package ca.uhn.fhir.context.support;
import org.apache.commons.lang3.Validate;
import java.util.Objects;
/**
* Options for ValueSet expansion
*
@ -126,4 +128,23 @@ public class ValueSetExpansionOptions {
myDisplayLanguage = theDisplayLanguage;
return this;
}
@Override
public boolean equals(Object theO) {
if (this == theO) return true;
if (!(theO instanceof ValueSetExpansionOptions)) return false;
ValueSetExpansionOptions that = (ValueSetExpansionOptions) theO;
return myFailOnMissingCodeSystem == that.myFailOnMissingCodeSystem
&& myCount == that.myCount
&& myOffset == that.myOffset
&& myIncludeHierarchy == that.myIncludeHierarchy
&& Objects.equals(myFilter, that.myFilter)
&& Objects.equals(myDisplayLanguage, that.myDisplayLanguage);
}
@Override
public int hashCode() {
return Objects.hash(
myFailOnMissingCodeSystem, myCount, myOffset, myIncludeHierarchy, myFilter, myDisplayLanguage);
}
}

View File

@ -46,7 +46,11 @@ public @interface Hook {
* and allowable values can be positive or negative or 0.
* <p>
* If no order is specified, or the order is set to <code>0</code> (the default order),
* the order specified at the interceptor type level will take precedence.
* the order specified at the {@link Interceptor#order() interceptor type level} will be used.
* </p>
* <p>
* Note that if two hook methods have the same order, then the order of execution is undefined. If
* order is important, then an order must always be explicitly stated.
* </p>
*/
int order() default Interceptor.DEFAULT_ORDER;

View File

@ -19,6 +19,7 @@
*/
package ca.uhn.fhir.interceptor.api;
import java.util.List;
import java.util.function.Supplier;
public interface IBaseInterceptorBroadcaster<POINTCUT extends IPointcut> {
@ -73,4 +74,15 @@ public interface IBaseInterceptorBroadcaster<POINTCUT extends IPointcut> {
* @since 4.0.0
*/
boolean hasHooks(POINTCUT thePointcut);
List<IInvoker> getInvokersForPointcut(POINTCUT thePointcut);
interface IInvoker extends Comparable<IInvoker> {
Object invoke(HookParams theParams);
int getOrder();
Object getInterceptor();
}
}

View File

@ -27,6 +27,8 @@ public interface IPointcut {
@Nonnull
Class<?> getReturnType();
Class<?> getBooleanReturnTypeForEnum();
@Nonnull
List<String> getParameterTypes();

View File

@ -26,6 +26,7 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.validation.ValidationResult;
import jakarta.annotation.Nonnull;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseConformance;
import java.io.Writer;
@ -3107,6 +3108,10 @@ public enum Pointcut implements IPointcut {
@Nonnull Class<?> theReturnType,
@Nonnull ExceptionHandlingSpec theExceptionHandlingSpec,
String... theParameterTypes) {
// This enum uses the lowercase-b boolean type to indicate boolean return pointcuts
Validate.isTrue(!theReturnType.equals(Boolean.class), "Return type Boolean not allowed here, must be boolean");
myReturnType = theReturnType;
myExceptionHandlingSpec = theExceptionHandlingSpec;
myParameterTypes = Collections.unmodifiableList(Arrays.asList(theParameterTypes));
@ -3132,6 +3137,11 @@ public enum Pointcut implements IPointcut {
return myReturnType;
}
@Override
public Class<?> getBooleanReturnTypeForEnum() {
return boolean.class;
}
@Override
@Nonnull
public List<String> getParameterTypes() {

View File

@ -20,6 +20,7 @@
package ca.uhn.fhir.interceptor.executor;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IBaseInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.IBaseInterceptorService;
@ -57,12 +58,13 @@ import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
public abstract class BaseInterceptorService<POINTCUT extends Enum<POINTCUT> & IPointcut>
implements IBaseInterceptorService<POINTCUT>, IBaseInterceptorBroadcaster<POINTCUT> {
private static final Logger ourLog = LoggerFactory.getLogger(BaseInterceptorService.class);
@ -74,12 +76,11 @@ public abstract class BaseInterceptorService<POINTCUT extends Enum<POINTCUT> & I
AttributeKey.stringKey("hapifhir.interceptor.method_name");
private final List<Object> myInterceptors = new ArrayList<>();
private final ListMultimap<POINTCUT, BaseInvoker> myGlobalInvokers = ArrayListMultimap.create();
private final ListMultimap<POINTCUT, BaseInvoker> myAnonymousInvokers = ArrayListMultimap.create();
private final ListMultimap<POINTCUT, IInvoker> myGlobalInvokers = ArrayListMultimap.create();
private final ListMultimap<POINTCUT, IInvoker> myAnonymousInvokers = ArrayListMultimap.create();
private final Object myRegistryMutex = new Object();
private final Class<POINTCUT> myPointcutType;
private volatile EnumSet<POINTCUT> myRegisteredPointcuts;
private String myName;
private boolean myWarnOnInterceptorWithNoHooks = true;
/**
@ -93,10 +94,11 @@ public abstract class BaseInterceptorService<POINTCUT extends Enum<POINTCUT> & I
* Constructor
*
* @param theName The name for this registry (useful for troubleshooting)
* @deprecated The name parameter is not used for anything
*/
@Deprecated(since = "8.0.0", forRemoval = true)
public BaseInterceptorService(Class<POINTCUT> thePointcutType, String theName) {
super();
myName = theName;
myPointcutType = thePointcutType;
rebuildRegisteredPointcutSet();
}
@ -113,13 +115,17 @@ public abstract class BaseInterceptorService<POINTCUT extends Enum<POINTCUT> & I
return myInterceptors;
}
public void setName(String theName) {
myName = theName;
/**
* @deprecated This value is not used anywhere
*/
@Deprecated(since = "8.0.0", forRemoval = true)
public void setName(@SuppressWarnings("unused") String theName) {
// nothing
}
protected void registerAnonymousInterceptor(POINTCUT thePointcut, Object theInterceptor, BaseInvoker theInvoker) {
Validate.notNull(thePointcut);
Validate.notNull(theInterceptor);
Validate.notNull(thePointcut, "thePointcut must not be null");
Validate.notNull(theInterceptor, "theInterceptor must not be null");
synchronized (myRegistryMutex) {
myAnonymousInvokers.put(thePointcut, theInvoker);
if (!isInterceptorAlreadyRegistered(theInterceptor)) {
@ -179,9 +185,9 @@ public abstract class BaseInterceptorService<POINTCUT extends Enum<POINTCUT> & I
}
private void unregisterInterceptorsIf(
Predicate<Object> theShouldUnregisterFunction, ListMultimap<POINTCUT, BaseInvoker> theGlobalInvokers) {
Predicate<Object> theShouldUnregisterFunction, ListMultimap<POINTCUT, IInvoker> theGlobalInvokers) {
synchronized (myRegistryMutex) {
for (Map.Entry<POINTCUT, BaseInvoker> nextInvoker : new ArrayList<>(theGlobalInvokers.entries())) {
for (Map.Entry<POINTCUT, IInvoker> nextInvoker : new ArrayList<>(theGlobalInvokers.entries())) {
if (theShouldUnregisterFunction.test(nextInvoker.getValue().getInterceptor())) {
unregisterInterceptor(nextInvoker.getValue().getInterceptor());
}
@ -265,7 +271,7 @@ public abstract class BaseInterceptorService<POINTCUT extends Enum<POINTCUT> & I
assert haveAppropriateParams(thePointcut, theParams);
assert thePointcut.getReturnType() != void.class;
return doCallHooks(thePointcut, theParams, null);
return doCallHooks(thePointcut, theParams);
}
@Override
@ -282,116 +288,47 @@ public abstract class BaseInterceptorService<POINTCUT extends Enum<POINTCUT> & I
assert haveAppropriateParams(thePointcut, theParams);
assert thePointcut.getReturnType() == void.class || thePointcut.getReturnType() == getBooleanReturnType();
Object retValObj = doCallHooks(thePointcut, theParams, true);
Object retValObj = doCallHooks(thePointcut, theParams);
retValObj = defaultIfNull(retValObj, true);
return (Boolean) retValObj;
}
private Object doCallHooks(POINTCUT thePointcut, HookParams theParams, Object theRetVal) {
// use new list for loop to avoid ConcurrentModificationException in case invoker gets added while looping
List<BaseInvoker> invokers = new ArrayList<>(getInvokersForPointcut(thePointcut));
/*
* Call each hook in order
*/
for (BaseInvoker nextInvoker : invokers) {
Object nextOutcome = nextInvoker.invoke(theParams);
Class<?> pointcutReturnType = thePointcut.getReturnType();
if (pointcutReturnType.equals(getBooleanReturnType())) {
Boolean nextOutcomeAsBoolean = (Boolean) nextOutcome;
if (Boolean.FALSE.equals(nextOutcomeAsBoolean)) {
ourLog.trace("callHooks({}) for invoker({}) returned false", thePointcut, nextInvoker);
theRetVal = false;
break;
} else {
theRetVal = true;
}
} else if (!pointcutReturnType.equals(void.class)) {
if (nextOutcome != null) {
theRetVal = nextOutcome;
break;
}
}
}
return theRetVal;
private Object doCallHooks(POINTCUT thePointcut, HookParams theParams) {
List<IInvoker> invokers = getInvokersForPointcut(thePointcut);
return callInvokers(thePointcut, theParams, invokers);
}
@VisibleForTesting
List<Object> getInterceptorsWithInvokersForPointcut(POINTCUT thePointcut) {
return getInvokersForPointcut(thePointcut).stream()
.map(BaseInvoker::getInterceptor)
.map(IInvoker::getInterceptor)
.collect(Collectors.toList());
}
/**
* Returns an ordered list of invokers for the given pointcut. Note that
* a new and stable list is returned to.. do whatever you want with it.
* Returns a list of all invokers registered for the given pointcut. The list
* is ordered by the invoker order (specified on the {@link Interceptor#order()}
* and {@link Hook#order()} values.
*
* @return The list returned by this method will always be a newly created list, so it will be stable and can be modified.
*/
private List<BaseInvoker> getInvokersForPointcut(POINTCUT thePointcut) {
List<BaseInvoker> invokers;
@Override
public List<IInvoker> getInvokersForPointcut(POINTCUT thePointcut) {
List<IInvoker> invokers;
synchronized (myRegistryMutex) {
List<BaseInvoker> globalInvokers = myGlobalInvokers.get(thePointcut);
List<BaseInvoker> anonymousInvokers = myAnonymousInvokers.get(thePointcut);
List<BaseInvoker> threadLocalInvokers = null;
invokers = union(globalInvokers, anonymousInvokers, threadLocalInvokers);
List<IInvoker> globalInvokers = myGlobalInvokers.get(thePointcut);
List<IInvoker> anonymousInvokers = myAnonymousInvokers.get(thePointcut);
invokers = union(Arrays.asList(globalInvokers, anonymousInvokers));
}
return invokers;
}
/**
* First argument must be the global invoker list!!
*/
@SafeVarargs
private List<BaseInvoker> union(List<BaseInvoker>... theInvokersLists) {
List<BaseInvoker> haveOne = null;
boolean haveMultiple = false;
for (List<BaseInvoker> nextInvokerList : theInvokersLists) {
if (nextInvokerList == null || nextInvokerList.isEmpty()) {
continue;
}
if (haveOne == null) {
haveOne = nextInvokerList;
} else {
haveMultiple = true;
}
}
if (haveOne == null) {
return Collections.emptyList();
}
List<BaseInvoker> retVal;
if (!haveMultiple) {
// The global list doesn't need to be sorted every time since it's sorted on
// insertion each time. Doing so is a waste of cycles..
if (haveOne == theInvokersLists[0]) {
retVal = haveOne;
} else {
retVal = new ArrayList<>(haveOne);
retVal.sort(Comparator.naturalOrder());
}
} else {
retVal = Arrays.stream(theInvokersLists)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.sorted()
.collect(Collectors.toList());
}
return retVal;
}
/**
* Only call this when assertions are enabled, it's expensive
*/
final boolean haveAppropriateParams(POINTCUT thePointcut, HookParams theParams) {
public static boolean haveAppropriateParams(IPointcut thePointcut, HookParams theParams) {
if (theParams.getParamsForType().values().size()
!= thePointcut.getParameterTypes().size()) {
throw new IllegalArgumentException(Msg.code(1909)
@ -430,7 +367,7 @@ public abstract class BaseInterceptorService<POINTCUT extends Enum<POINTCUT> & I
}
private List<HookInvoker> scanInterceptorAndAddToInvokerMultimap(
Object theInterceptor, ListMultimap<POINTCUT, BaseInvoker> theInvokers) {
Object theInterceptor, ListMultimap<POINTCUT, IInvoker> theInvokers) {
Class<?> interceptorClass = theInterceptor.getClass();
int typeOrder = determineOrder(interceptorClass);
@ -452,7 +389,7 @@ public abstract class BaseInterceptorService<POINTCUT extends Enum<POINTCUT> & I
// Make sure we're always sorted according to the order declared in @Order
for (POINTCUT nextPointcut : theInvokers.keys()) {
List<BaseInvoker> nextInvokerList = theInvokers.get(nextPointcut);
List<IInvoker> nextInvokerList = theInvokers.get(nextPointcut);
nextInvokerList.sort(Comparator.naturalOrder());
}
@ -483,6 +420,108 @@ public abstract class BaseInterceptorService<POINTCUT extends Enum<POINTCUT> & I
protected abstract Optional<HookDescriptor> scanForHook(Method nextMethod);
public static Object callInvokers(IPointcut thePointcut, HookParams theParams, List<IInvoker> invokers) {
Object retVal = null;
/*
* Call each hook in order
*/
for (IInvoker nextInvoker : invokers) {
Object nextOutcome = nextInvoker.invoke(theParams);
Class<?> pointcutReturnType = thePointcut.getReturnType();
if (pointcutReturnType.equals(thePointcut.getBooleanReturnTypeForEnum())) {
Boolean nextOutcomeAsBoolean = (Boolean) nextOutcome;
if (Boolean.FALSE.equals(nextOutcomeAsBoolean)) {
ourLog.trace("callHooks({}) for invoker({}) returned false", thePointcut, nextInvoker);
retVal = false;
break;
} else {
retVal = true;
}
} else if (!pointcutReturnType.equals(void.class)) {
if (nextOutcome != null) {
retVal = nextOutcome;
break;
}
}
}
return retVal;
}
/**
* First argument must be the global invoker list!!
*/
public static List<IInvoker> union(List<List<IInvoker>> theInvokersLists) {
List<IInvoker> haveOne = null;
boolean haveMultiple = false;
for (List<IInvoker> nextInvokerList : theInvokersLists) {
if (nextInvokerList == null || nextInvokerList.isEmpty()) {
continue;
}
if (haveOne == null) {
haveOne = nextInvokerList;
} else {
haveMultiple = true;
}
}
if (haveOne == null) {
return Collections.emptyList();
}
List<IInvoker> retVal;
if (!haveMultiple) {
// The global list doesn't need to be sorted every time since it's sorted on
// insertion each time. Doing so is a waste of cycles..
if (haveOne == theInvokersLists.get(0)) {
retVal = haveOne;
} else {
retVal = new ArrayList<>(haveOne);
retVal.sort(Comparator.naturalOrder());
}
} else {
int totalSize = 0;
for (List<IInvoker> list : theInvokersLists) {
totalSize += list.size();
}
retVal = new ArrayList<>(totalSize);
for (List<IInvoker> list : theInvokersLists) {
retVal.addAll(list);
}
retVal.sort(Comparator.naturalOrder());
}
return retVal;
}
protected static <T extends Annotation> Optional<T> findAnnotation(
AnnotatedElement theObject, Class<T> theHookClass) {
T annotation;
if (theObject instanceof Method) {
annotation = MethodUtils.getAnnotation((Method) theObject, theHookClass, true, true);
} else {
annotation = theObject.getAnnotation(theHookClass);
}
return Optional.ofNullable(annotation);
}
private static int determineOrder(Class<?> theInterceptorClass) {
return findAnnotation(theInterceptorClass, Interceptor.class)
.map(Interceptor::order)
.orElse(Interceptor.DEFAULT_ORDER);
}
private static String toErrorString(List<String> theParameterTypes) {
return theParameterTypes.stream().sorted().collect(Collectors.joining(","));
}
private class HookInvoker extends BaseInvoker {
private final Method myMethod;
@ -501,10 +540,11 @@ public abstract class BaseInterceptorService<POINTCUT extends Enum<POINTCUT> & I
myMethod = theHookMethod;
Class<?> returnType = theHookMethod.getReturnType();
if (myPointcut.getReturnType().equals(getBooleanReturnType())) {
if (myPointcut.getReturnType().equals(myPointcut.getBooleanReturnTypeForEnum())) {
Validate.isTrue(
getBooleanReturnType().equals(returnType) || void.class.equals(returnType),
"Method does not return boolean or void: %s",
myPointcut.getBooleanReturnTypeForEnum().equals(returnType) || void.class.equals(returnType),
"Method does not return %s or void: %s",
myPointcut.getBooleanReturnTypeForEnum().getSimpleName(),
theHookMethod);
} else if (myPointcut.getReturnType().equals(void.class)) {
Validate.isTrue(void.class.equals(returnType), "Method does not return void: %s", theHookMethod);
@ -541,7 +581,7 @@ public abstract class BaseInterceptorService<POINTCUT extends Enum<POINTCUT> & I
* @return Returns true/false if the hook method returns a boolean, returns true otherwise
*/
@Override
Object invoke(HookParams theParams) {
public Object invoke(HookParams theParams) {
Object[] args = new Object[myParameterTypes.length];
for (int i = 0; i < myParameterTypes.length; i++) {
@ -610,7 +650,7 @@ public abstract class BaseInterceptorService<POINTCUT extends Enum<POINTCUT> & I
}
}
protected abstract static class BaseInvoker implements Comparable<BaseInvoker> {
public abstract static class BaseInvoker implements IInvoker {
private final int myOrder;
private final Object myInterceptor;
@ -620,36 +660,19 @@ public abstract class BaseInterceptorService<POINTCUT extends Enum<POINTCUT> & I
myOrder = theOrder;
}
@Override
public Object getInterceptor() {
return myInterceptor;
}
abstract Object invoke(HookParams theParams);
@Override
public int getOrder() {
return myOrder;
}
@Override
public int compareTo(BaseInvoker theInvoker) {
return myOrder - theInvoker.myOrder;
public int compareTo(IInvoker theInvoker) {
return myOrder - theInvoker.getOrder();
}
}
protected static <T extends Annotation> Optional<T> findAnnotation(
AnnotatedElement theObject, Class<T> theHookClass) {
T annotation;
if (theObject instanceof Method) {
annotation = MethodUtils.getAnnotation((Method) theObject, theHookClass, true, true);
} else {
annotation = theObject.getAnnotation(theHookClass);
}
return Optional.ofNullable(annotation);
}
private static int determineOrder(Class<?> theInterceptorClass) {
return findAnnotation(theInterceptorClass, Interceptor.class)
.map(Interceptor::order)
.orElse(Interceptor.DEFAULT_ORDER);
}
private static String toErrorString(List<String> theParameterTypes) {
return theParameterTypes.stream().sorted().collect(Collectors.joining(","));
}
}

View File

@ -64,8 +64,8 @@ public class InterceptorService extends BaseInterceptorService<Pointcut>
@Override
public void registerAnonymousInterceptor(Pointcut thePointcut, int theOrder, IAnonymousInterceptor theInterceptor) {
Validate.notNull(thePointcut);
Validate.notNull(theInterceptor);
Validate.notNull(thePointcut, "thePointcut must not be null");
Validate.notNull(theInterceptor, "theInterceptor must not be null");
BaseInvoker invoker = new AnonymousLambdaInvoker(thePointcut, theInterceptor, theOrder);
registerAnonymousInterceptor(thePointcut, theInterceptor, invoker);
}
@ -81,7 +81,7 @@ public class InterceptorService extends BaseInterceptorService<Pointcut>
}
@Override
Object invoke(HookParams theParams) {
public Object invoke(HookParams theParams) {
myHook.invoke(myPointcut, theParams);
return true;
}

View File

@ -20,6 +20,7 @@
package ca.uhn.fhir.narrative2;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.util.BundleUtil;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.instance.model.api.IBaseBundle;
@ -42,7 +43,8 @@ public class NarrativeGeneratorTemplateUtils {
* Given a Bundle as input, are any entries present with a given resource type
*/
public boolean bundleHasEntriesWithResourceType(IBaseBundle theBaseBundle, String theResourceType) {
FhirContext ctx = theBaseBundle.getStructureFhirVersionEnum().newContextCached();
FhirVersionEnum fhirVersionEnum = theBaseBundle.getStructureFhirVersionEnum();
FhirContext ctx = FhirContext.forCached(fhirVersionEnum);
List<Pair<String, IBaseResource>> entryResources =
BundleUtil.getBundleEntryUrlsAndResources(ctx, theBaseBundle);
return entryResources.stream()

View File

@ -105,7 +105,6 @@ public abstract class BaseParser implements IParser {
private static final Set<String> notEncodeForContainedResource =
new HashSet<>(Arrays.asList("security", "versionId", "lastUpdated"));
private FhirTerser.ContainedResources myContainedResources;
private boolean myEncodeElementsAppliesToChildResourcesOnly;
private final FhirContext myContext;
private Collection<String> myDontEncodeElements;
@ -183,12 +182,15 @@ public abstract class BaseParser implements IParser {
}
private String determineReferenceText(
IBaseReference theRef, CompositeChildElement theCompositeChildElement, IBaseResource theResource) {
IBaseReference theRef,
CompositeChildElement theCompositeChildElement,
IBaseResource theResource,
EncodeContext theContext) {
IIdType ref = theRef.getReferenceElement();
if (isBlank(ref.getIdPart())) {
String reference = ref.getValue();
if (theRef.getResource() != null) {
IIdType containedId = getContainedResources().getResourceId(theRef.getResource());
IIdType containedId = theContext.getContainedResources().getResourceId(theRef.getResource());
if (containedId != null && !containedId.isEmpty()) {
if (containedId.isLocal()) {
reference = containedId.getValue();
@ -262,7 +264,8 @@ public abstract class BaseParser implements IParser {
@Override
public final void encodeResourceToWriter(IBaseResource theResource, Writer theWriter)
throws IOException, DataFormatException {
EncodeContext encodeContext = new EncodeContext(this, myContext.getParserOptions());
EncodeContext encodeContext =
new EncodeContext(this, myContext.getParserOptions(), new FhirTerser.ContainedResources());
encodeResourceToWriter(theResource, theWriter, encodeContext);
}
@ -285,7 +288,8 @@ public abstract class BaseParser implements IParser {
} else if (theElement instanceof IPrimitiveType) {
theWriter.write(((IPrimitiveType<?>) theElement).getValueAsString());
} else {
EncodeContext encodeContext = new EncodeContext(this, myContext.getParserOptions());
EncodeContext encodeContext =
new EncodeContext(this, myContext.getParserOptions(), new FhirTerser.ContainedResources());
encodeToWriter(theElement, theWriter, encodeContext);
}
}
@ -404,10 +408,6 @@ public abstract class BaseParser implements IParser {
return elementId;
}
FhirTerser.ContainedResources getContainedResources() {
return myContainedResources;
}
@Override
public Set<String> getDontStripVersionsFromReferencesAtPaths() {
return myDontStripVersionsFromReferencesAtPaths;
@ -539,10 +539,11 @@ public abstract class BaseParser implements IParser {
return mySuppressNarratives;
}
protected boolean isChildContained(BaseRuntimeElementDefinition<?> childDef, boolean theIncludedResource) {
protected boolean isChildContained(
BaseRuntimeElementDefinition<?> childDef, boolean theIncludedResource, EncodeContext theContext) {
return (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES
|| childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST)
&& getContainedResources().isEmpty() == false
&& theContext.getContainedResources().isEmpty() == false
&& theIncludedResource == false;
}
@ -788,7 +789,8 @@ public abstract class BaseParser implements IParser {
*/
if (next instanceof IBaseReference) {
IBaseReference nextRef = (IBaseReference) next;
String refText = determineReferenceText(nextRef, theCompositeChildElement, theResource);
String refText =
determineReferenceText(nextRef, theCompositeChildElement, theResource, theEncodeContext);
if (!StringUtils.equals(refText, nextRef.getReferenceElement().getValue())) {
if (retVal == theValues) {
@ -980,7 +982,7 @@ public abstract class BaseParser implements IParser {
return true;
}
protected void containResourcesInReferences(IBaseResource theResource) {
protected void containResourcesInReferences(IBaseResource theResource, EncodeContext theContext) {
/*
* If a UUID is present in Bundle.entry.fullUrl but no value is present
@ -1003,7 +1005,7 @@ public abstract class BaseParser implements IParser {
}
}
myContainedResources = getContext().newTerser().containResources(theResource);
theContext.setContainedResources(getContext().newTerser().containResources(theResource));
}
static class ChildNameAndDef {
@ -1034,8 +1036,12 @@ public abstract class BaseParser implements IParser {
private final List<EncodeContextPath> myEncodeElementPaths;
private final Set<String> myEncodeElementsAppliesToResourceTypes;
private final List<EncodeContextPath> myDontEncodeElementPaths;
private FhirTerser.ContainedResources myContainedResources;
public EncodeContext(BaseParser theParser, ParserOptions theParserOptions) {
public EncodeContext(
BaseParser theParser,
ParserOptions theParserOptions,
FhirTerser.ContainedResources theContainedResources) {
Collection<String> encodeElements = theParser.myEncodeElements;
Collection<String> dontEncodeElements = theParser.myDontEncodeElements;
if (isSummaryMode()) {
@ -1058,6 +1064,8 @@ public abstract class BaseParser implements IParser {
dontEncodeElements.stream().map(EncodeContextPath::new).collect(Collectors.toList());
}
myContainedResources = theContainedResources;
myEncodeElementsAppliesToResourceTypes =
ParserUtil.determineApplicableResourceTypesForTerserPaths(myEncodeElementPaths);
}
@ -1065,6 +1073,14 @@ public abstract class BaseParser implements IParser {
private Map<Key, List<BaseParser.CompositeChildElement>> getCompositeChildrenCache() {
return myCompositeChildrenCache;
}
public FhirTerser.ContainedResources getContainedResources() {
return myContainedResources;
}
public void setContainedResources(FhirTerser.ContainedResources theContainedResources) {
myContainedResources = theContainedResources;
}
}
protected class CompositeChildElement {

View File

@ -54,6 +54,7 @@ import ca.uhn.fhir.parser.json.JsonLikeStructure;
import ca.uhn.fhir.parser.json.jackson.JacksonStructure;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.util.ElementUtil;
import ca.uhn.fhir.util.FhirTerser;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.text.WordUtils;
@ -386,12 +387,14 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
case CONTAINED_RESOURCE_LIST:
case CONTAINED_RESOURCES: {
List<IBaseResource> containedResources = getContainedResources().getContainedResources();
List<IBaseResource> containedResources =
theEncodeContext.getContainedResources().getContainedResources();
if (containedResources.size() > 0) {
beginArray(theEventWriter, theChildName);
for (IBaseResource next : containedResources) {
IIdType resourceId = getContainedResources().getResourceId(next);
IIdType resourceId =
theEncodeContext.getContainedResources().getResourceId(next);
String value = resourceId.getValue();
encodeResourceToJsonStreamWriter(
theResDef,
@ -554,7 +557,8 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
if (nextValue == null || nextValue.isEmpty()) {
if (nextValue instanceof BaseContainedDt) {
if (theContainedResource || getContainedResources().isEmpty()) {
if (theContainedResource
|| theEncodeContext.getContainedResources().isEmpty()) {
continue;
}
} else {
@ -838,7 +842,8 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
+ theResource.getStructureFhirVersionEnum());
}
EncodeContext encodeContext = new EncodeContext(this, getContext().getParserOptions());
EncodeContext encodeContext =
new EncodeContext(this, getContext().getParserOptions(), new FhirTerser.ContainedResources());
String resourceName = getContext().getResourceType(theResource);
encodeContext.pushPath(resourceName, true);
doEncodeResourceToJsonLikeWriter(theResource, theJsonLikeWriter, encodeContext);
@ -894,7 +899,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
if (!theContainedResource) {
containResourcesInReferences(theResource);
containResourcesInReferences(theResource, theEncodeContext);
}
RuntimeResourceDefinition resDef = getContext().getResourceDefinition(theResource);

View File

@ -191,7 +191,7 @@ public class RDFParser extends BaseParser {
}
if (!containedResource) {
containResourcesInReferences(resource);
containResourcesInReferences(resource, encodeContext);
}
if (!(resource instanceof IAnyResource)) {
@ -354,7 +354,7 @@ public class RDFParser extends BaseParser {
try {
if (element == null || element.isEmpty()) {
if (!isChildContained(childDef, includedResource)) {
if (!isChildContained(childDef, includedResource, theEncodeContext)) {
return rdfModel;
}
}

View File

@ -295,7 +295,7 @@ public class XmlParser extends BaseParser {
try {
if (theElement == null || theElement.isEmpty()) {
if (isChildContained(childDef, theIncludedResource)) {
if (isChildContained(childDef, theIncludedResource, theEncodeContext)) {
// We still want to go in..
} else {
return;
@ -359,8 +359,10 @@ public class XmlParser extends BaseParser {
* theEventWriter.writeStartElement("contained"); encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(next.getId().getValue()));
* theEventWriter.writeEndElement(); }
*/
for (IBaseResource next : getContainedResources().getContainedResources()) {
IIdType resourceId = getContainedResources().getResourceId(next);
for (IBaseResource next :
theEncodeContext.getContainedResources().getContainedResources()) {
IIdType resourceId =
theEncodeContext.getContainedResources().getResourceId(next);
theEventWriter.writeStartElement("contained");
String value = resourceId.getValue();
encodeResourceToXmlStreamWriter(
@ -682,7 +684,7 @@ public class XmlParser extends BaseParser {
}
if (!theContainedResource) {
containResourcesInReferences(theResource);
containResourcesInReferences(theResource, theEncodeContext);
}
theEventWriter.writeStartElement(resDef.getName());

View File

@ -28,6 +28,8 @@ import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
import com.google.common.annotations.Beta;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseConformance;
import org.hl7.fhir.instance.model.api.IBaseParameters;
@ -231,6 +233,23 @@ public interface Repository {
// Querying starts here
/**
* Searches this repository
*
* @see <a href="https://www.hl7.org/fhir/http.html#search">FHIR search</a>
*
* @param <B> a Bundle type
* @param <T> a Resource type
* @param bundleType the class of the Bundle type to return
* @param resourceType the class of the Resource type to search
* @param searchParameters the searchParameters for this search
* @return a Bundle with the results of the search
*/
default <B extends IBaseBundle, T extends IBaseResource> B search(
Class<B> bundleType, Class<T> resourceType, Multimap<String, List<IQueryParameterType>> searchParameters) {
return this.search(bundleType, resourceType, searchParameters, Collections.emptyMap());
}
/**
* Searches this repository
*
@ -264,9 +283,32 @@ public interface Repository {
<B extends IBaseBundle, T extends IBaseResource> B search(
Class<B> bundleType,
Class<T> resourceType,
Map<String, List<IQueryParameterType>> searchParameters,
Multimap<String, List<IQueryParameterType>> searchParameters,
Map<String, String> headers);
/**
* Searches this repository
*
* @see <a href="https://www.hl7.org/fhir/http.html#search">FHIR search</a>
*
* @param <B> a Bundle type
* @param <T> a Resource type
* @param bundleType the class of the Bundle type to return
* @param resourceType the class of the Resource type to search
* @param searchParameters the searchParameters for this search
* @param headers headers for this request, typically key-value pairs of HTTP headers
* @return a Bundle with the results of the search
*/
default <B extends IBaseBundle, T extends IBaseResource> B search(
Class<B> bundleType,
Class<T> resourceType,
Map<String, List<IQueryParameterType>> searchParameters,
Map<String, String> headers) {
ArrayListMultimap<String, List<IQueryParameterType>> multimap = ArrayListMultimap.create();
searchParameters.forEach(multimap::put);
return this.search(bundleType, resourceType, multimap, headers);
}
// Paging starts here
/**

View File

@ -342,6 +342,8 @@ public class Constants {
*/
public static final String HIBERNATE_INTEGRATION_ENVERS_ENABLED = "hibernate.integration.envers.enabled";
public static final String OPENTELEMETRY_BASE_NAME = "io.hapifhir";
static {
CHARSET_UTF8 = StandardCharsets.UTF_8;
CHARSET_US_ASCII = StandardCharsets.ISO_8859_1;

View File

@ -38,7 +38,6 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
import ca.uhn.fhir.model.base.composite.BaseContainedDt;
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.DataFormatException;
import com.google.common.collect.Lists;
@ -61,6 +60,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
@ -70,6 +70,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@ -77,16 +78,28 @@ import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.substring;
public class FhirTerser {
private static final Pattern COMPARTMENT_MATCHER_PATH =
Pattern.compile("([a-zA-Z.]+)\\.where\\(resolve\\(\\) is ([a-zA-Z]+)\\)");
private static final String USER_DATA_KEY_CONTAIN_RESOURCES_COMPLETED =
FhirTerser.class.getName() + "_CONTAIN_RESOURCES_COMPLETED";
private final FhirContext myContext;
/**
* This comparator sorts IBaseReferences, and places any that are missing an ID at the end. Those with an ID go to the front.
*/
private static final Comparator<IBaseReference> REFERENCES_WITH_IDS_FIRST =
Comparator.nullsLast(Comparator.comparing(ref -> {
if (ref.getResource() == null) return true;
if (ref.getResource().getIdElement() == null) return true;
if (ref.getResource().getIdElement().getValue() == null) return true;
return false;
}));
public FhirTerser(FhirContext theContext) {
super();
myContext = theContext;
@ -1418,6 +1431,13 @@ public class FhirTerser {
private void containResourcesForEncoding(
ContainedResources theContained, IBaseResource theResource, boolean theModifyResource) {
List<IBaseReference> allReferences = getAllPopulatedChildElementsOfType(theResource, IBaseReference.class);
// Note that we process all contained resources that have arrived here with an ID contained resources first, so
// that we don't accidentally auto-assign an ID
// which may collide with a resource we have yet to process.
// See: https://github.com/hapifhir/hapi-fhir/issues/6403
allReferences.sort(REFERENCES_WITH_IDS_FIRST);
for (IBaseReference next : allReferences) {
IBaseResource resource = next.getResource();
if (resource == null && next.getReferenceElement().isLocal()) {
@ -1437,11 +1457,11 @@ public class FhirTerser {
IBaseResource resource = next.getResource();
if (resource != null) {
if (resource.getIdElement().isEmpty() || resource.getIdElement().isLocal()) {
if (theContained.getResourceId(resource) != null) {
// Prevent infinite recursion if there are circular loops in the contained resources
IIdType id = theContained.addContained(resource);
if (id == null) {
continue;
}
IIdType id = theContained.addContained(resource);
if (theModifyResource) {
getContainedResourceList(theResource).add(resource);
next.setReference(id.getValue());
@ -1768,8 +1788,6 @@ public class FhirTerser {
}
public static class ContainedResources {
private long myNextContainedId = 1;
private List<IBaseResource> myResourceList;
private IdentityHashMap<IBaseResource, IIdType> myResourceToIdMap;
private Map<String, IBaseResource> myExistingIdToContainedResourceMap;
@ -1782,6 +1800,11 @@ public class FhirTerser {
}
public IIdType addContained(IBaseResource theResource) {
if (this.getResourceId(theResource) != null) {
// Prevent infinite recursion if there are circular loops in the contained resources
return null;
}
IIdType existing = getResourceToIdMap().get(theResource);
if (existing != null) {
return existing;
@ -1789,16 +1812,7 @@ public class FhirTerser {
IIdType newId = theResource.getIdElement();
if (isBlank(newId.getValue())) {
newId.setValue("#" + myNextContainedId++);
} else {
// Avoid auto-assigned contained IDs colliding with pre-existing ones
String idPart = newId.getValue();
if (substring(idPart, 0, 1).equals("#")) {
idPart = idPart.substring(1);
if (StringUtils.isNumeric(idPart)) {
myNextContainedId = Long.parseLong(idPart) + 1;
}
}
newId.setValue("#" + UUID.randomUUID());
}
getResourceToIdMap().put(theResource, newId);
@ -1862,45 +1876,5 @@ public class FhirTerser {
public boolean hasExistingIdToContainedResource() {
return myExistingIdToContainedResourceMap != null;
}
public void assignIdsToContainedResources() {
if (!getContainedResources().isEmpty()) {
/*
* The idea with the code block below:
*
* We want to preserve any IDs that were user-assigned, so that if it's really
* important to someone that their contained resource have the ID of #FOO
* or #1 we will keep that.
*
* For any contained resources where no ID was assigned by the user, we
* want to manually create an ID but make sure we don't reuse an existing ID.
*/
Set<String> ids = new HashSet<>();
// Gather any user assigned IDs
for (IBaseResource nextResource : getContainedResources()) {
if (getResourceToIdMap().get(nextResource) != null) {
ids.add(getResourceToIdMap().get(nextResource).getValue());
}
}
// Automatically assign IDs to the rest
for (IBaseResource nextResource : getContainedResources()) {
while (getResourceToIdMap().get(nextResource) == null) {
String nextCandidate = "#" + myNextContainedId;
myNextContainedId++;
if (!ids.add(nextCandidate)) {
continue;
}
getResourceToIdMap().put(nextResource, new IdDt(nextCandidate));
}
}
}
}
}
}

View File

@ -33,7 +33,7 @@ public class SleepUtil {
@SuppressWarnings("BusyWait")
public void sleepAtLeast(long theMillis, boolean theLogProgress) {
long start = System.currentTimeMillis();
while (System.currentTimeMillis() <= start + theMillis) {
while (System.currentTimeMillis() < start + theMillis) {
try {
long timeSinceStarted = System.currentTimeMillis() - start;
long timeToSleep = Math.max(0, theMillis - timeSinceStarted);

View File

@ -170,6 +170,7 @@ public enum VersionEnum {
V7_5_0,
V7_6_0,
V7_6_1,
V7_7_0,
V7_8_0;

View File

@ -0,0 +1,62 @@
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.util.adapters;
import jakarta.annotation.Nonnull;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
public class AdapterManager implements IAdapterManager {
public static final AdapterManager INSTANCE = new AdapterManager();
Set<IAdapterFactory> myAdapterFactories = new HashSet<>();
/**
* Hidden to force shared use of the public INSTANCE.
*/
AdapterManager() {}
public <T> @Nonnull Optional<T> getAdapter(Object theObject, Class<T> theTargetType) {
// todo this can be sped up with a cache of type->Factory.
return myAdapterFactories.stream()
.filter(nextFactory -> nextFactory.getAdapters().stream().anyMatch(theTargetType::isAssignableFrom))
.flatMap(nextFactory -> {
var adapter = nextFactory.getAdapter(theObject, theTargetType);
// can't use Optional.stream() because of our Android target is API level 26/JDK 8.
if (adapter.isPresent()) {
return Stream.of(adapter.get());
} else {
return Stream.empty();
}
})
.findFirst();
}
public void registerFactory(@Nonnull IAdapterFactory theFactory) {
myAdapterFactories.add(theFactory);
}
public void unregisterFactory(@Nonnull IAdapterFactory theFactory) {
myAdapterFactories.remove(theFactory);
}
}

View File

@ -0,0 +1,48 @@
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.util.adapters;
import java.util.Optional;
public class AdapterUtils {
/**
* Main entry point for adapter calls.
* Implements three conversions: cast to the target type, use IAdaptable if present, or lastly try the AdapterManager.INSTANCE.
* @param theObject the object to be adapted
* @param theTargetType the type of the adapter requested
*/
static <T> Optional<T> adapt(Object theObject, Class<T> theTargetType) {
if (theTargetType.isInstance(theObject)) {
//noinspection unchecked
return Optional.of((T) theObject);
}
if (theObject instanceof IAdaptable) {
IAdaptable adaptable = (IAdaptable) theObject;
var adapted = adaptable.getAdapter(theTargetType);
if (adapted.isPresent()) {
return adapted;
}
}
return AdapterManager.INSTANCE.getAdapter(theObject, theTargetType);
}
}

View File

@ -0,0 +1,38 @@
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.util.adapters;
import jakarta.annotation.Nonnull;
import java.util.Optional;
/**
* Generic version of Eclipse IAdaptable interface.
*/
public interface IAdaptable {
/**
* Get an adapter of requested type.
* @param theTargetType the desired type of the adapter
* @return an adapter of theTargetType if possible, or empty.
*/
default <T> @Nonnull Optional<T> getAdapter(@Nonnull Class<T> theTargetType) {
return AdapterUtils.adapt(this, theTargetType);
}
}

View File

@ -0,0 +1,44 @@
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.util.adapters;
import java.util.Collection;
import java.util.Optional;
/**
* Interface for external service that builds adaptors for targets.
*/
public interface IAdapterFactory {
/**
* Build an adaptor for the target.
* May return empty() even if the target type is listed in getAdapters() when
* the factory fails to convert a particular instance.
*
* @param theObject the object to be adapted.
* @param theAdapterType the target type
* @return the adapter, if possible.
*/
<T> Optional<T> getAdapter(Object theObject, Class<T> theAdapterType);
/**
* @return the collection of adapter target types handled by this factory.
*/
Collection<Class<?>> getAdapters();
}

View File

@ -0,0 +1,29 @@
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.util.adapters;
import java.util.Optional;
/**
* Get an adaptor
*/
public interface IAdapterManager {
<T> Optional<T> getAdapter(Object theTarget, Class<T> theAdapter);
}

View File

@ -0,0 +1,39 @@
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
/**
* Implements the Adapter pattern to allow external classes to extend/adapt existing classes.
* Useful for extending interfaces that are closed to modification, or restricted for classpath reasons.
* <p>
* For clients, the main entry point is {@link ca.uhn.fhir.util.adapters.AdapterUtils#adapt(java.lang.Object, java.lang.Class)}
* which will attempt to cast to the target type, or build an adapter of the target type.
* </p>
* <p>
* For implementors, you can support adaptation via two mechanisms:
* <ul>
* <li>by implementing {@link ca.uhn.fhir.util.adapters.IAdaptable} directly on a class to provide supported adapters,
* <li>or when the class is closed to direct modification, you can implement
* an instance of {@link ca.uhn.fhir.util.adapters.IAdapterFactory} and register
* it with the public {@link ca.uhn.fhir.util.adapters.AdapterManager#INSTANCE}.</li>
* </ul>
* The AdapterUtils.adapt() supports both of these.
* </p>
* Inspired by the Eclipse runtime.
*/
package ca.uhn.fhir.util.adapters;

View File

@ -0,0 +1,78 @@
package ca.uhn.fhir.util.adapters;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
public class AdapterManagerTest {
AdapterManager myAdapterManager = new AdapterManager();
@AfterAll
static void tearDown() {
assertThat(AdapterManager.INSTANCE.myAdapterFactories)
.withFailMessage("Don't dirty the public instance").isEmpty();
}
@Test
void testRegisterFactory_providesAdapter() {
// given
myAdapterManager.registerFactory(new StringToIntFactory());
// when
var result = myAdapterManager.getAdapter("22", Integer.class);
// then
assertThat(result).contains(22);
}
@Test
void testRegisterFactory_wrongTypeStillEmpty() {
// given
myAdapterManager.registerFactory(new StringToIntFactory());
// when
var result = myAdapterManager.getAdapter("22", Float.class);
// then
assertThat(result).isEmpty();
}
@Test
void testUnregisterFactory_providesEmpty() {
// given active factory, now gone.
StringToIntFactory factory = new StringToIntFactory();
myAdapterManager.registerFactory(factory);
myAdapterManager.getAdapter("22", Integer.class);
myAdapterManager.unregisterFactory(factory);
// when
var result = myAdapterManager.getAdapter("22", Integer.class);
// then
assertThat(result).isEmpty();
}
static class StringToIntFactory implements IAdapterFactory {
@Override
public <T> Optional<T> getAdapter(Object theObject, Class<T> theAdapterType) {
if (theObject instanceof String s) {
if (theAdapterType.isAssignableFrom(Integer.class)) {
@SuppressWarnings("unchecked")
T i = (T) Integer.valueOf(s);
return Optional.of(i);
}
}
return Optional.empty();
}
public Collection<Class<?>> getAdapters() {
return List.of(Integer.class);
}
}
}

View File

@ -0,0 +1,123 @@
package ca.uhn.fhir.util.adapters;
import jakarta.annotation.Nonnull;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import java.util.Collection;
import java.util.Optional;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
class AdapterUtilsTest {
final private IAdapterFactory myTestFactory = new TestAdaptorFactory();
@AfterEach
void tearDown() {
AdapterManager.INSTANCE.unregisterFactory(myTestFactory);
}
@Test
void testNullDoesNotAdapt() {
// when
var adapted = AdapterUtils.adapt(null, InterfaceA.class);
// then
assertThat(adapted).isEmpty();
}
@Test
void testAdaptObjectImplementingInterface() {
// given
var object = new ClassB();
// when
var adapted = AdapterUtils.adapt(object, InterfaceA.class);
// then
assertThat(adapted)
.isPresent()
.get().isInstanceOf(InterfaceA.class);
assertThat(adapted.get()).withFailMessage("Use object since it implements interface").isSameAs(object);
}
@Test
void testAdaptObjectImplementingAdaptorSupportingInterface() {
// given
var object = new SelfAdaptableClass();
// when
var adapted = AdapterUtils.adapt(object, InterfaceA.class);
// then
assertThat(adapted)
.isPresent()
.get().isInstanceOf(InterfaceA.class);
}
@Test
void testAdaptObjectViaAdapterManager() {
// given
var object = new ManagerAdaptableClass();
AdapterManager.INSTANCE.registerFactory(myTestFactory);
// when
var adapted = AdapterUtils.adapt(object, InterfaceA.class);
// then
assertThat(adapted)
.isPresent()
.get().isInstanceOf(InterfaceA.class);
}
interface InterfaceA {
}
static class ClassB implements InterfaceA {
}
/** class that can adapt itself to IAdaptable */
static class SelfAdaptableClass implements IAdaptable {
@Nonnull
@Override
public <T> Optional<T> getAdapter(@Nonnull Class<T> theTargetType) {
if (theTargetType.isAssignableFrom(InterfaceA.class)) {
T value = theTargetType.cast(buildInterfaceAWrapper(this));
return Optional.of(value);
}
return Optional.empty();
}
}
private static @Nonnull InterfaceA buildInterfaceAWrapper(Object theObject) {
return new InterfaceA() {};
}
/** Class that relies on an external IAdapterFactory */
static class ManagerAdaptableClass {
}
static class TestAdaptorFactory implements IAdapterFactory {
@Override
public <T> Optional<T> getAdapter(Object theObject, Class<T> theAdapterType) {
if (theObject instanceof ManagerAdaptableClass && theAdapterType == InterfaceA.class) {
T adapter = theAdapterType.cast(buildInterfaceAWrapper(theObject));
return Optional.of(adapter);
}
return Optional.empty();
}
@Override
public Collection<Class<?>> getAdapters() {
return Set.of(InterfaceA.class);
}
}
}

View File

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-bom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<packaging>pom</packaging>
<name>HAPI FHIR BOM</name>
@ -12,7 +12,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -668,7 +668,7 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
protected void parseFhirContext(CommandLine theCommandLine) throws ParseException {
FhirVersionEnum versionEnum = parseFhirVersion(theCommandLine);
myFhirCtx = versionEnum.newContext();
myFhirCtx = FhirContext.forVersion(versionEnum);
}
public abstract void run(CommandLine theCommandLine) throws ParseException, ExecutionException;

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -98,7 +98,7 @@ public class VersionCanonicalizer {
private final FhirContext myContext;
public VersionCanonicalizer(FhirVersionEnum theTargetVersion) {
this(theTargetVersion.newContextCached());
this(FhirContext.forCached(theTargetVersion));
}
public VersionCanonicalizer(FhirContext theTargetContext) {

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -40,7 +40,6 @@ import jakarta.annotation.Nonnull;
import jakarta.servlet.ServletException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.NpmPackageValidationSupport;
@ -343,7 +342,8 @@ public class ValidatorExamples {
// START SNIPPET: validateSupplyProfiles
FhirContext ctx = FhirContext.forR4();
// Create a chain that will hold our modules
// Create a chain that will hold our modules and caches the
// values they supply
ValidationSupportChain supportChain = new ValidationSupportChain();
// DefaultProfileValidationSupport supplies base FHIR definitions. This is generally required
@ -368,12 +368,9 @@ public class ValidatorExamples {
// Add the custom definitions to the chain
supportChain.addValidationSupport(prePopulatedSupport);
// Wrap the chain in a cache to improve performance
CachingValidationSupport cache = new CachingValidationSupport(supportChain);
// Create a validator using the FhirInstanceValidator module. We can use this
// validator to perform validation
FhirInstanceValidator validatorModule = new FhirInstanceValidator(cache);
FhirInstanceValidator validatorModule = new FhirInstanceValidator(supportChain);
FhirValidator validator = ctx.newValidator().registerValidatorModule(validatorModule);
ValidationResult result = validator.validateWithResult(input);
// END SNIPPET: validateSupplyProfiles
@ -403,12 +400,9 @@ public class ValidatorExamples {
remoteTermSvc.setBaseUrl("http://hapi.fhir.org/baseR4");
supportChain.addValidationSupport(remoteTermSvc);
// Wrap the chain in a cache to improve performance
CachingValidationSupport cache = new CachingValidationSupport(supportChain);
// Create a validator using the FhirInstanceValidator module. We can use this
// validator to perform validation
FhirInstanceValidator validatorModule = new FhirInstanceValidator(cache);
FhirInstanceValidator validatorModule = new FhirInstanceValidator(supportChain);
FhirValidator validator = ctx.newValidator().registerValidatorModule(validatorModule);
ValidationResult result = validator.validateWithResult(input);
// END SNIPPET: validateUsingRemoteTermSvr
@ -462,12 +456,11 @@ public class ValidatorExamples {
new CommonCodeSystemsTerminologyService(ctx),
new InMemoryTerminologyServerValidationSupport(ctx),
new SnapshotGeneratingValidationSupport(ctx));
CachingValidationSupport validationSupport = new CachingValidationSupport(validationSupportChain);
// Create a validator. Note that for good performance you can create as many validator objects
// as you like, but you should reuse the same validation support object in all of the,.
FhirValidator validator = ctx.newValidator();
FhirInstanceValidator instanceValidator = new FhirInstanceValidator(validationSupport);
FhirInstanceValidator instanceValidator = new FhirInstanceValidator(validationSupportChain);
validator.registerValidatorModule(instanceValidator);
// Create a test patient to validate

View File

@ -0,0 +1,4 @@
---
type: change
jira: SMILE-9161
title: "Contained resources which arrive without assigned IDs are now assigned GUIDs, as opposed to monotonically increasing numeric IDs. This avoids a whole class of issues related to processing order and collisions."

View File

@ -0,0 +1,6 @@
---
type: fix
jira: SMILE-9161
title: "Fixed a rare bug in the JSON Parser, wherein client-assigned contained resource IDs could collide with server-assigned contained IDs. For example if a
resource had a client-assigned contained ID of `#2`, and a contained resource with no ID, then depending on the processing order, the parser could occasionally
provide duplicate contained resource IDs, leading to non-deterministic behaviour."

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 6419
title: "Previously, on Postgres, the `$reindex` operation with `optimizeStorage` set to `ALL_VERSIONS` would process
only a subset of versions if there were more than 100 versions to be processed for a resource. This has been fixed
so that all versions of the resource are now processed."

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 6420
title: "Previously, when the `$reindex` operation is run for a single FHIR resource with `optimizeStorage` set to
`ALL_VERSIONS`, none of the versions of the resource were processed in `hfj_res_ver` table. This has been fixed."

View File

@ -0,0 +1,7 @@
---
type: fix
issue: 6422
title: "Previously, since 7.4.4 the validation issue detail codes were not translated correctly for Remote Terminology
validateCode calls. The detail code used was `invalid-code` for all use-cases which resulted in profile binding strength
not being applied to the issue severity as expected when validating resources against a profile.
This has been fixed and issue detail codes are translated correctly."

View File

@ -0,0 +1,8 @@
---
type: fix
issue: 6440
title: "Previously, if an `IInterceptorBroadcaster` was set in a `RequestDetails` object,
`STORAGE_PRECHECK_FOR_CACHED_SEARCH` hooks that were registered to that `IInterceptorBroadcaster` were not
called. Also, if an `IInterceptorBroadcaster` was set in the `RequestDetails` object, the boolean return value of the hooks
registered to that `IInterceptorBroadcaster` were not taken into account. This second issue existed for all pointcuts
that returned a boolean type, not just for `STORAGE_PRECHECK_FOR_CACHED_SEARCH`. These issues have now been fixed."

View File

@ -0,0 +1,4 @@
---
type: add
issue: 6445
title: "Add Multimap versions of the search() methods to Repository to support queries like `Patient?_tag=a&_tag=b`"

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 6451
jira: SMILE-9089
title: "Previously, activating `BulkDataImport` job would not change jobs status to `RUNNING`,
causing it to be processed multiple times instead of single time. This has been fixed."

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 6467
title: "Fixed an incompatibility between Hibernate Search and Lucene versions that caused ValueSet expansion to fail
when Hibernate Search was configured to use Lucene."

View File

@ -0,0 +1,4 @@
---
type: perf
issue: 6489
title: "Change the migrator to avoid table locks when adding an index. This allows systems to continue running during upgrade."

View File

@ -4,7 +4,7 @@
title: "The version of a few dependencies have been bumped to more recent versions
(dependent HAPI modules listed in brackets):
<ul>
<li>org.hl7.fhir.core (Base): 6.3.18 -&gt; 6.3.25</li>
<li>org.hl7.fhir.core (Base): 6.3.18 -&gt; 6.4.0</li>
<li>Bower/Moment.js (hapi-fhir-testpage-overlay): 2.27.0 -&gt; 2.29.4</li>
<li>htmlunit (Base): 3.9.0 -&gt; 3.11.0</li>
<li>Elasticsearch (Base): 8.11.1 -&gt; 8.14.3</li>

View File

@ -0,0 +1,8 @@
---
type: fix
issue: 6404
title: "Searches using fulltext search that combined `_lastUpdated` query parameter with
any other (supported) fulltext query parameter would find no matches, even
if matches existed.
This has been corrected.
"

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 6502
backport: 7.6.1
title: "Support ReferenceParam in addition to UriParam for `_profile` in queries using the SearchParameterMap to match the change in the specification from DSTU3 to R4."

View File

@ -0,0 +1,13 @@
---
type: perf
issue: 6508
title: "The ValidationSupportChain module has been rewritten to improve validator performance. This change:
* Adds new caching capabilities to ValidationSupportChain. This is an improvement over the previous separate caching module because the chain can now remember which entries in the cache responded affirmative to `isValueSetSupported()` and will therefore be more efficient about trying entries in the chain. It also makes debugging much less confusing as there is less recursion and the caches don't use loadingCache.
* Importantly, the caching in ValidationSupportChain caches negative lookups (i.e. items that could not be found by URL) as well as positive lookups. This is a change from the historical caching behaviour.
* Changes ValidationSupportChain to never expire StructureDefinition entries in the cache, which is needed because the validator makes assumptions about structuredefinitions never changing. Fixes #6424.
* Modifies `VersionSpecificWorkerContextWrapper` so that it doesn't use a separate cache and instead relies on the caching provided by ValidationSupportChain. This class previously used a cache because it converts arbitrary versions of FHIR StructureDefinitions into the canonical version required by the validator (R5), but these converted versions are now stored in the userdata map of objects returned by and cached by ValidationSupportChain. This makes the caching more predictable since there is only one cache to track.
* Adds OpenTelemetry support to ValidationSupportChain, with metrics for tracking the cache size.
* Deprecates CachingValidationSupport since caching is now provided by ValidationSupportChain. CachingValidationSupport is now just a passthrough and should be removed from applications. It will be removed from the library in a future release.
* Removes ConceptMap caching from TermReachSvcImpl, as this caching is both redundant and inefficient as it operates within a database transaction.
These changes result in very significant performance improvements when performing validation in the JPA server. Throughput improvements of 1000% have been recorded in benchmarking use cases involving large profiles and remote terminology services enabled. Many other validation use cases should see significant improvements as well."

View File

@ -0,0 +1,8 @@
---
type: add
issue: 6511
title: "Interceptors can be defined against the registry on the RestfulServer, or on
the registry in the JPA repository. Because these are separate registries, the order()
attribute on the Hook annotation isn't correctly processed today across the two
registries. The CompositeInterceptorRegistry has been reworked so ordering will be
respected across both registries."

View File

@ -0,0 +1,4 @@
---
type: remove
issue: 6512
title: "The methods on FhirVersionEnum which produces a FhirContext (newContext() ,and newContextCached()) have been deprecated, and will be removed."

View File

@ -0,0 +1,4 @@
# Fulltext Search with _lastUpdated Filter
Fulltext searches have been updated to support `_lastUpdated` search parameter. A reindexing of Search Parameters
is required to migrate old data to support the `_lastUpdated` search parameter.

View File

@ -48,24 +48,24 @@ Additional parameters have been added to support CQL evaluation.
The following parameters are supported for the `Questionnaire/$populate` operation:
| Parameter | Type | Description |
|---------------------|---------------|-------------|
| questionnaire | Questionnaire | The Questionnaire to populate. Used when the operation is invoked at the 'type' level. |
| canonical | canonical | The canonical identifier for the Questionnaire (optionally version-specific). |
| url | uri | Canonical URL of the Questionnaire when invoked at the resource type level. This is exclusive with the questionnaire and canonical parameters. |
| version | string | Version of the Questionnaire when invoked at the resource type level. This is exclusive with the questionnaire and canonical parameters. |
| subject | Reference | The resource that is to be the QuestionnaireResponse.subject. The QuestionnaireResponse instance will reference the provided subject. |
| context | | Resources containing information to be used to help populate the QuestionnaireResponse. |
| context.name | string | The name of the launchContext or root Questionnaire variable the passed content should be used as for population purposes. The name SHALL correspond to a launchContext or variable delared at the root of the Questionnaire. |
| context.reference | Reference | The actual resource (or resources) to use as the value of the launchContext or variable. |
| local | boolean | Whether the server should use what resources and other knowledge it has about the referenced subject when pre-populating answers to questions. |
| launchContext | Extension | The [Questionnaire Launch Context](https://hl7.org/fhir/uv/sdc/StructureDefinition-sdc-questionnaire-launchContext.html) extension containing Resources that provide context for form processing logic (pre-population) when creating/displaying/editing a QuestionnaireResponse. |
| parameters | Parameters | Any input parameters defined in libraries referenced by the Questionnaire. |
| useServerData | boolean | Whether to use data from the server performing the evaluation. |
| data | Bundle | Data to be made available during CQL evaluation. |
| dataEndpoint | Endpoint | An endpoint to use to access data referenced by retrieve operations in libraries referenced by the Questionnaire. |
| contentEndpoint | Endpoint | An endpoint to use to access content (i.e. libraries) referenced by the Questionnaire. |
| terminologyEndpoint | Endpoint | An endpoint to use to access terminology (i.e. valuesets, codesystems, and membership testing) referenced by the Questionnaire. |
| Parameter | Type | Description |
|---------------------|--------------------|-------------|
| questionnaire | Questionnaire | The Questionnaire to populate. Used when the operation is invoked at the 'type' level. |
| canonical | canonical | The canonical identifier for the Questionnaire (optionally version-specific). |
| url | uri | Canonical URL of the Questionnaire when invoked at the resource type level. This is exclusive with the questionnaire and canonical parameters. |
| version | string | Version of the Questionnaire when invoked at the resource type level. This is exclusive with the questionnaire and canonical parameters. |
| subject | Reference | The resource that is to be the QuestionnaireResponse.subject. The QuestionnaireResponse instance will reference the provided subject. |
| context | | Resources containing information to be used to help populate the QuestionnaireResponse. |
| context.name | string | The name of the launchContext or root Questionnaire variable the passed content should be used as for population purposes. The name SHALL correspond to a launchContext or variable declared at the root of the Questionnaire. |
| context.content | Reference/Resource | The actual resource (or reference) to use as the value of the launchContext or variable. |
| local | boolean | Whether the server should use what resources and other knowledge it has about the referenced subject when pre-populating answers to questions. |
| launchContext | Extension | The [Questionnaire Launch Context](https://hl7.org/fhir/uv/sdc/StructureDefinition-sdc-questionnaire-launchContext.html) extension containing Resources that provide context for form processing logic (pre-population) when creating/displaying/editing a QuestionnaireResponse. |
| parameters | Parameters | Any input parameters defined in libraries referenced by the Questionnaire. |
| useServerData | boolean | Whether to use data from the server performing the evaluation. |
| data | Bundle | Data to be made available during CQL evaluation. |
| dataEndpoint | Endpoint | An endpoint to use to access data referenced by retrieve operations in libraries referenced by the Questionnaire. |
| contentEndpoint | Endpoint | An endpoint to use to access content (i.e. libraries) referenced by the Questionnaire. |
| terminologyEndpoint | Endpoint | An endpoint to use to access terminology (i.e. valuesets, codesystems, and membership testing) referenced by the Questionnaire. |
## Extract

View File

@ -16,6 +16,21 @@ There are a several implementations of the [IValidationSupport](/hapi-fhir/apido
This module can be used to combine multiple implementations together so that for every request, each support class instance in the chain is tried in sequence. Note that nearly all methods in the [IValidationSupport](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/context/support/IValidationSupport.html) interface are permitted to return `null` if they are not able to service a particular method call. So for example, if a call to the [`validateCode`](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/context/support/IValidationSupport.html#validateCode(ca.uhn.fhir.context.support.ValidationSupportContext,ca.uhn.fhir.context.support.ConceptValidationOptions,java.lang.String,java.lang.String,java.lang.String,java.lang.String)) method is made, the validator will try each module in the chain until one of them returns a non-null response.
The following chaining logic is used:
* Calls to `fetchAll...` methods such as `fetchAllConformanceResources()` and `fetchAllStructureDefinitions()` will call every method in the chain in order, and aggregate the results into a single list to return.
* Calls to fetch or validate codes, such as `validateCode(...)` and `lookupCode(...)` will first test each module in the chain using the`isCodeSystemSupported(...)` or `isValueSetSupported(...)` methods (depending on whether a ValueSet URL is present in the method parameters) and will invoke any methods in the chain which return that they can handle the given CodeSystem/ValueSet URL. The first non-null value returned by a method in the chain that can support the URL will be returned to the caller.
* All other methods will invoke the method in the chain in order, and will return immediately as soon as a non-null value is returned.
The following caching logic is used if caching is enabled using `CacheConfiguration`. You can use `CacheConfiguration.disabled()` if you want to disable caching.
* Calls to fetch StructureDefinitions including `fetchAllStructureDefinitions()` and `fetchStructureDefinition(...)` are cached in a non-expiring cache. This is because the `FhirInstanceValidator` module makes assumptions that these objects will not change for the lifetime of the validator for performance reasons.
* Calls to all other `fetchAll...` methods including `fetchAllConformanceResources()` and `fetchAllSearchParameters()` cache their results in an expiring cache, but will refresh that cache asynchronously.
* Results of `generateSnapshot(...)` are not cached, as this method is generally called in contexts where the results are cached.
* Results of all other methods are stored in an expiring cache.
Note that caching functionality used to be provided by a separate provider called {@literal CachingValidationSupport} but that functionality has been moved into this class as of HAPI FHIR 8.0.0, because it is possible to provide a more efficient chain when these functions are combined.
# DefaultProfileValidationSupport
[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-base/undefined/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.html) / [Source](https://github.com/hapifhir/hapi-fhir/blob/master/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.java)
@ -44,12 +59,6 @@ This module contains a series of HashMaps that store loaded conformance resource
This module can be used to load FHIR NPM Packages and supply the conformance resources within them to the validator. See [Validating Using Packages](./instance_validator.html#packages) for am example of how to use this module.
# CachingValidationSupport
[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java)
This module caches results of calls to a wrapped service implementation for a period of time. This class can be a significant help in terms of performance if you are loading conformance resources or performing terminology operations from a database or disk, but it also has value even for purely in-memory validation since validating codes against a ValueSet can require the expansion of that ValueSet.
# SnapshotGeneratingValidationSupport
[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/SnapshotGeneratingValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/SnapshotGeneratingValidationSupport.java)
@ -161,6 +170,12 @@ This validation support module may be placed at the end of a ValidationSupportCh
Note that this module must also be activated by calling [setAllowNonExistentCodeSystem(true)](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.html#setAllowNonExistentCodeSystem(boolean)) in order to specify that unknown code systems should be allowed.
# CachingValidationSupport
[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java)
This module is deprecated and no longer provides any functionality. Caching is provided by [ValidationSupportChain](#validationsupportchain).
# Recipes

View File

@ -11,7 +11,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -27,7 +27,6 @@ import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.model.PersistentIdToForcedIdMap;
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
import ca.uhn.fhir.jpa.bulk.export.api.IBulkExportProcessor;
@ -349,10 +348,9 @@ public class JpaBulkExportProcessor implements IBulkExportProcessor<JpaPid> {
* Get a ISearchBuilder for the given resource type.
*/
protected ISearchBuilder<JpaPid> getSearchBuilderForResourceType(String theResourceType) {
IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(theResourceType);
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResourceType);
Class<? extends IBaseResource> typeClass = def.getImplementingClass();
return mySearchBuilderFactory.newSearchBuilder(dao, theResourceType, typeClass);
return mySearchBuilderFactory.newSearchBuilder(theResourceType, typeClass);
}
protected RuntimeSearchParam getPatientSearchParamForCurrentResourceType(String theResourceType) {

View File

@ -217,6 +217,12 @@ public class BulkDataImportSvcImpl implements IBulkDataImportSvc, IHasScheduledJ
String biJobId = null;
try {
biJobId = processJob(bulkImportJobEntity);
// set job status to RUNNING so it would not be processed again
myTxTemplate.execute(t -> {
bulkImportJobEntity.setStatus(BulkImportJobStatusEnum.RUNNING);
myJobDao.save(bulkImportJobEntity);
return null;
});
} catch (Exception e) {
ourLog.error("Failure while preparing bulk export extract", e);
myTxTemplate.execute(t -> {
@ -256,6 +262,7 @@ public class BulkDataImportSvcImpl implements IBulkDataImportSvc, IHasScheduledJ
}
@Override
@Transactional
public JobInfo getJobStatus(String theBiJobId) {
BulkImportJobEntity theJob = findJobByBiJobId(theBiJobId);
return new JobInfo()

View File

@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.jpa.api.IDaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.config.util.ResourceCountCacheUtil;
import ca.uhn.fhir.jpa.config.util.ValidationSupportConfigUtil;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.search.HSearchSortHelperImpl;
@ -32,15 +31,12 @@ import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl;
import ca.uhn.fhir.jpa.util.ResourceCountCache;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
import ca.uhn.fhir.rest.api.IResourceSupportedSvc;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
@Configuration
@Import({JpaConfig.class})
@ -64,12 +60,6 @@ public class HapiJpaConfig {
return new StaleSearchDeletingSvcImpl();
}
@Primary
@Bean
public CachingValidationSupport validationSupportChain(JpaValidationSupportChain theJpaValidationSupportChain) {
return ValidationSupportConfigUtil.newCachingValidationSupport(theJpaValidationSupportChain);
}
@Bean
public DatabaseBackedPagingProvider databaseBackedPagingProvider() {
return new DatabaseBackedPagingProvider();

View File

@ -168,6 +168,7 @@ import ca.uhn.fhir.jpa.term.config.TermCodeSystemConfig;
import ca.uhn.fhir.jpa.util.JpaHapiTransactionService;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.jpa.util.PersistenceContextProvider;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
import ca.uhn.fhir.jpa.validation.ResourceLoaderImpl;
import ca.uhn.fhir.jpa.validation.ValidationSettings;
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
@ -185,6 +186,7 @@ import ca.uhn.fhir.util.MetaTagSorterAlphabetical;
import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
import jakarta.annotation.Nullable;
import org.hl7.fhir.common.hapi.validation.support.UnknownCodeSystemWarningValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@ -230,11 +232,26 @@ public class JpaConfig {
public static final String PERSISTED_JPA_SEARCH_FIRST_PAGE_BUNDLE_PROVIDER =
"PersistedJpaSearchFirstPageBundleProvider";
public static final String HISTORY_BUILDER = "HistoryBuilder";
public static final String DEFAULT_PROFILE_VALIDATION_SUPPORT = "myDefaultProfileValidationSupport";
private static final String HAPI_DEFAULT_SCHEDULER_GROUP = "HAPI";
@Autowired
public JpaStorageSettings myStorageSettings;
@Autowired
private FhirContext myFhirContext;
@Bean
public ValidationSupportChain.CacheConfiguration validationSupportChainCacheConfiguration() {
return ValidationSupportChain.CacheConfiguration.defaultValues();
}
@Bean(name = JpaConfig.JPA_VALIDATION_SUPPORT_CHAIN)
@Primary
public IValidationSupport jpaValidationSupportChain() {
return new JpaValidationSupportChain(myFhirContext, validationSupportChainCacheConfiguration());
}
@Bean("myDaoRegistry")
public DaoRegistry daoRegistry() {
return new DaoRegistry();

View File

@ -25,7 +25,6 @@ import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IDao;
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
@ -158,10 +157,8 @@ public class SearchConfig {
@Bean(name = ISearchBuilder.SEARCH_BUILDER_BEAN_NAME)
@Scope("prototype")
public ISearchBuilder newSearchBuilder(
IDao theDao, String theResourceName, Class<? extends IBaseResource> theResourceType) {
public ISearchBuilder newSearchBuilder(String theResourceName, Class<? extends IBaseResource> theResourceType) {
return new SearchBuilder(
theDao,
theResourceName,
myStorageSettings,
myEntityManagerFactory,

View File

@ -20,31 +20,31 @@
package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.JpaPersistedResourceValidationSupport;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
import ca.uhn.fhir.jpa.validation.ValidatorPolicyAdvisor;
import ca.uhn.fhir.jpa.validation.ValidatorResourceFetcher;
import ca.uhn.fhir.validation.IInstanceValidatorModule;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.common.hapi.validation.validator.HapiToHl7OrgDstu2ValidatingSupportWrapper;
import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
@Configuration
public class ValidationSupportConfig {
@Bean(name = "myDefaultProfileValidationSupport")
public DefaultProfileValidationSupport defaultProfileValidationSupport(FhirContext theFhirContext) {
return new DefaultProfileValidationSupport(theFhirContext);
@Autowired
private FhirContext myFhirContext;
@Bean(name = JpaConfig.DEFAULT_PROFILE_VALIDATION_SUPPORT)
public DefaultProfileValidationSupport defaultProfileValidationSupport() {
return new DefaultProfileValidationSupport(myFhirContext);
}
@Bean
@ -56,11 +56,6 @@ public class ValidationSupportConfig {
return retVal;
}
@Bean(name = JpaConfig.JPA_VALIDATION_SUPPORT_CHAIN)
public JpaValidationSupportChain jpaValidationSupportChain(FhirContext theFhirContext) {
return new JpaValidationSupportChain(theFhirContext);
}
@Bean(name = JpaConfig.JPA_VALIDATION_SUPPORT)
public IValidationSupport jpaValidationSupport(FhirContext theFhirContext) {
return new JpaPersistedResourceValidationSupport(theFhirContext);
@ -68,26 +63,13 @@ public class ValidationSupportConfig {
@Bean(name = "myInstanceValidator")
public IInstanceValidatorModule instanceValidator(
FhirContext theFhirContext,
CachingValidationSupport theCachingValidationSupport,
ValidationSupportChain theValidationSupportChain,
IValidationSupport theValidationSupport,
DaoRegistry theDaoRegistry) {
if (theFhirContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
FhirInstanceValidator val = new FhirInstanceValidator(theCachingValidationSupport);
val.setValidatorResourceFetcher(
jpaValidatorResourceFetcher(theFhirContext, theValidationSupport, theDaoRegistry));
val.setValidatorPolicyAdvisor(jpaValidatorPolicyAdvisor());
val.setBestPracticeWarningLevel(BestPracticeWarningLevel.Warning);
val.setValidationSupport(theCachingValidationSupport);
return val;
} else {
CachingValidationSupport cachingValidationSupport = new CachingValidationSupport(
new HapiToHl7OrgDstu2ValidatingSupportWrapper(theValidationSupportChain));
FhirInstanceValidator retVal = new FhirInstanceValidator(cachingValidationSupport);
retVal.setBestPracticeWarningLevel(BestPracticeWarningLevel.Warning);
return retVal;
}
FhirContext theFhirContext, IValidationSupport theValidationSupportChain, DaoRegistry theDaoRegistry) {
FhirInstanceValidator val = new FhirInstanceValidator(theValidationSupportChain);
val.setValidatorResourceFetcher(
jpaValidatorResourceFetcher(theFhirContext, theValidationSupportChain, theDaoRegistry));
val.setValidatorPolicyAdvisor(jpaValidatorPolicyAdvisor());
val.setBestPracticeWarningLevel(BestPracticeWarningLevel.Warning);
return val;
}
@Bean

View File

@ -1,43 +0,0 @@
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.jpa.config.util;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
public final class ValidationSupportConfigUtil {
private ValidationSupportConfigUtil() {}
public static CachingValidationSupport newCachingValidationSupport(
JpaValidationSupportChain theJpaValidationSupportChain) {
return newCachingValidationSupport(theJpaValidationSupportChain, false);
}
public static CachingValidationSupport newCachingValidationSupport(
JpaValidationSupportChain theJpaValidationSupportChain,
boolean theIsEnabledValidationForCodingsLogicalAnd) {
// Short timeout for code translation because TermConceptMappingSvcImpl has its own caching
CachingValidationSupport.CacheTimeouts cacheTimeouts =
CachingValidationSupport.CacheTimeouts.defaultValues().setTranslateCodeMillis(1000);
return new CachingValidationSupport(
theJpaValidationSupportChain, cacheTimeouts, theIsEnabledValidationForCodingsLogicalAnd);
}
}

View File

@ -1057,8 +1057,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
// Interceptor broadcast: JPA_PERFTRACE_INFO
if (!presenceCount.isEmpty()) {
if (CompositeInterceptorBroadcaster.hasHooks(
Pointcut.JPA_PERFTRACE_INFO, myInterceptorBroadcaster, theRequest)) {
IInterceptorBroadcaster compositeBroadcaster =
CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, theRequest);
if (compositeBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_INFO)) {
StorageProcessingMessage message = new StorageProcessingMessage();
message.setMessage(
"For " + entity.getIdDt().toUnqualifiedVersionless().getValue() + " added "
@ -1068,8 +1069,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(StorageProcessingMessage.class, message);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_INFO, params);
compositeBroadcaster.callHooks(Pointcut.JPA_PERFTRACE_INFO, params);
}
}
}
@ -1092,8 +1092,10 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
// Interceptor broadcast: JPA_PERFTRACE_INFO
if (!searchParamAddRemoveCount.isEmpty()) {
if (CompositeInterceptorBroadcaster.hasHooks(
Pointcut.JPA_PERFTRACE_INFO, myInterceptorBroadcaster, theRequest)) {
IInterceptorBroadcaster compositeBroadcaster =
CompositeInterceptorBroadcaster.newCompositeBroadcaster(
myInterceptorBroadcaster, theRequest);
if (compositeBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_INFO)) {
StorageProcessingMessage message = new StorageProcessingMessage();
message.setMessage("For "
+ entity.getIdDt().toUnqualifiedVersionless().getValue() + " added "
@ -1104,8 +1106,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(StorageProcessingMessage.class, message);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_INFO, params);
compositeBroadcaster.callHooks(Pointcut.JPA_PERFTRACE_INFO, params);
}
}
}
@ -1688,7 +1689,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
theEntity.setContentText(parseContentTextIntoWords(theContext, theResource));
if (myStorageSettings.isAdvancedHSearchIndexing()) {
ExtendedHSearchIndexData hSearchIndexData =
myFulltextSearchSvc.extractLuceneIndexData(theResource, theNewParams);
myFulltextSearchSvc.extractLuceneIndexData(theResource, theEntity, theNewParams);
theEntity.setLuceneIndexData(hSearchIndexData);
}
}

View File

@ -134,6 +134,7 @@ import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@ -226,15 +227,15 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Nullable
public static <T extends IBaseResource> T invokeStoragePreShowResources(
IInterceptorBroadcaster theInterceptorBroadcaster, RequestDetails theRequest, T retVal) {
if (CompositeInterceptorBroadcaster.hasHooks(
Pointcut.STORAGE_PRESHOW_RESOURCES, theInterceptorBroadcaster, theRequest)) {
IInterceptorBroadcaster compositeBroadcaster =
CompositeInterceptorBroadcaster.newCompositeBroadcaster(theInterceptorBroadcaster, theRequest);
if (compositeBroadcaster.hasHooks(Pointcut.STORAGE_PRESHOW_RESOURCES)) {
SimplePreResourceShowDetails showDetails = new SimplePreResourceShowDetails(retVal);
HookParams params = new HookParams()
.add(IPreResourceShowDetails.class, showDetails)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest);
CompositeInterceptorBroadcaster.doCallHooks(
theInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PRESHOW_RESOURCES, params);
compositeBroadcaster.callHooks(Pointcut.STORAGE_PRESHOW_RESOURCES, params);
//noinspection unchecked
retVal = (T) showDetails.getResource(
0); // TODO GGG/JA : getting resource 0 is interesting. We apparently allow null values in the list.
@ -250,15 +251,15 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
RequestDetails theRequest,
IIdType theId,
IBaseResource theResource) {
if (CompositeInterceptorBroadcaster.hasHooks(
Pointcut.STORAGE_PREACCESS_RESOURCES, theInterceptorBroadcaster, theRequest)) {
IInterceptorBroadcaster compositeBroadcaster =
CompositeInterceptorBroadcaster.newCompositeBroadcaster(theInterceptorBroadcaster, theRequest);
if (compositeBroadcaster.hasHooks(Pointcut.STORAGE_PREACCESS_RESOURCES)) {
SimplePreResourceAccessDetails accessDetails = new SimplePreResourceAccessDetails(theResource);
HookParams params = new HookParams()
.add(IPreResourceAccessDetails.class, accessDetails)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest);
CompositeInterceptorBroadcaster.doCallHooks(
theInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PREACCESS_RESOURCES, params);
compositeBroadcaster.callHooks(Pointcut.STORAGE_PREACCESS_RESOURCES, params);
if (accessDetails.isDontReturnResourceAtIndex(0)) {
throw new ResourceNotFoundException(Msg.code(1995) + "Resource " + theId + " is not known");
}
@ -1584,15 +1585,15 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
private Optional<T> invokeStoragePreAccessResources(RequestDetails theRequest, T theResource) {
if (CompositeInterceptorBroadcaster.hasHooks(
Pointcut.STORAGE_PREACCESS_RESOURCES, myInterceptorBroadcaster, theRequest)) {
IInterceptorBroadcaster compositeBroadcaster =
CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, theRequest);
if (compositeBroadcaster.hasHooks(Pointcut.STORAGE_PREACCESS_RESOURCES)) {
SimplePreResourceAccessDetails accessDetails = new SimplePreResourceAccessDetails(theResource);
HookParams params = new HookParams()
.add(IPreResourceAccessDetails.class, accessDetails)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PREACCESS_RESOURCES, params);
compositeBroadcaster.callHooks(Pointcut.STORAGE_PREACCESS_RESOURCES, params);
if (accessDetails.isDontReturnResourceAtIndex(0)) {
return Optional.empty();
}
@ -1697,9 +1698,15 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
if (theOptimizeStorageMode == ReindexParameters.OptimizeStorageModeEnum.ALL_VERSIONS) {
int pageSize = 100;
for (int page = 0; ((long) page * pageSize) < entity.getVersion(); page++) {
// We need to sort the pages, because we are updating the same data we are paging through.
// If not sorted explicitly, a database like Postgres returns the same data multiple times on
// different pages as the underlying data gets updated.
PageRequest pageRequest = PageRequest.of(page, pageSize, Sort.by("myId"));
Slice<ResourceHistoryTable> historyEntities =
myResourceHistoryTableDao.findForResourceIdAndReturnEntitiesAndFetchProvenance(
PageRequest.of(page, pageSize), entity.getId(), historyEntity.getVersion());
pageRequest, entity.getId(), historyEntity.getVersion());
for (ResourceHistoryTable next : historyEntities) {
reindexOptimizeStorageHistoryEntity(entity, next);
}
@ -2096,7 +2103,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
ISearchBuilder<JpaPid> builder =
mySearchBuilderFactory.newSearchBuilder(this, getResourceName(), getResourceType());
mySearchBuilderFactory.newSearchBuilder(getResourceName(), getResourceType());
List<JpaPid> ids = new ArrayList<>();
@ -2129,8 +2136,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(
theRequest, myResourceName, theParams, theConditionalOperationTargetOrNull);
ISearchBuilder<JpaPid> builder =
mySearchBuilderFactory.newSearchBuilder(this, getResourceName(), getResourceType());
ISearchBuilder<JpaPid> builder = mySearchBuilderFactory.newSearchBuilder(getResourceName(), getResourceType());
String uuid = UUID.randomUUID().toString();
@ -2168,7 +2174,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
.withPropagation(Propagation.REQUIRED)
.searchList(() -> {
ISearchBuilder<JpaPid> builder =
mySearchBuilderFactory.newSearchBuilder(this, getResourceName(), getResourceType());
mySearchBuilderFactory.newSearchBuilder(getResourceName(), getResourceType());
Stream<JpaPid> pidStream =
builder.createQueryStream(theParams, searchRuntimeDetails, theRequest, requestPartitionId);
@ -2184,7 +2190,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Nonnull
private Stream<T> pidsToResource(RequestDetails theRequest, Stream<JpaPid> pidStream) {
ISearchBuilder<JpaPid> searchBuilder =
mySearchBuilderFactory.newSearchBuilder(this, getResourceName(), getResourceType());
mySearchBuilderFactory.newSearchBuilder(getResourceName(), getResourceType());
@SuppressWarnings("unchecked")
Stream<T> resourceStream = (Stream<T>) new QueryChunker<>()
.chunk(pidStream, SearchBuilder.getMaximumPageSize())

View File

@ -205,7 +205,7 @@ public abstract class BaseHapiFhirSystemDao<T extends IBaseBundle, MT> extends B
*
* However, for realistic average workloads, this should reduce the number of round trips.
*/
if (idChunk.size() >= 2) {
if (!idChunk.isEmpty()) {
List<ResourceTable> entityChunk = prefetchResourceTableHistoryAndProvenance(idChunk);
if (thePreFetchIndexes) {

View File

@ -135,13 +135,13 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
@Override
public ExtendedHSearchIndexData extractLuceneIndexData(
IBaseResource theResource, ResourceIndexedSearchParams theNewParams) {
IBaseResource theResource, ResourceTable theEntity, ResourceIndexedSearchParams theNewParams) {
String resourceType = myFhirContext.getResourceType(theResource);
ResourceSearchParams activeSearchParams = mySearchParamRegistry.getActiveSearchParams(
resourceType, ISearchParamRegistry.SearchParamLookupContextEnum.SEARCH);
ExtendedHSearchIndexExtractor extractor = new ExtendedHSearchIndexExtractor(
myStorageSettings, myFhirContext, activeSearchParams, mySearchParamExtractor);
return extractor.extract(theResource, theNewParams);
return extractor.extract(theResource, theEntity, theNewParams);
}
@Override
@ -516,8 +516,9 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
*/
@SuppressWarnings("rawtypes")
private void logQuery(SearchQueryOptionsStep theQuery, RequestDetails theRequestDetails) {
if (CompositeInterceptorBroadcaster.hasHooks(
Pointcut.JPA_PERFTRACE_INFO, myInterceptorBroadcaster, theRequestDetails)) {
IInterceptorBroadcaster compositeBroadcaster =
CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, theRequestDetails);
if (compositeBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_INFO)) {
StorageProcessingMessage storageProcessingMessage = new StorageProcessingMessage();
String queryString = theQuery.toQuery().queryString();
storageProcessingMessage.setMessage(queryString);
@ -525,8 +526,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
.add(StorageProcessingMessage.class, storageProcessingMessage);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster, theRequestDetails, Pointcut.JPA_PERFTRACE_INFO, params);
compositeBroadcaster.callHooks(Pointcut.JPA_PERFTRACE_INFO, params);
}
}

View File

@ -48,6 +48,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@ -243,8 +246,23 @@ public class HistoryBuilder {
Subquery<Date> pastDateSubQuery = theQuery.subquery(Date.class);
Root<ResourceHistoryTable> subQueryResourceHistory = pastDateSubQuery.from(ResourceHistoryTable.class);
Expression myUpdatedMostRecent = theCriteriaBuilder.max(subQueryResourceHistory.get("myUpdated"));
/*
* This conversion from the Date in myRangeEndInclusive into a ZonedDateTime is an experiment -
* There is an intermittent test failure in testSearchHistoryWithAtAndGtParameters() that I can't
* figure out. But I've added a ton of logging to the error it fails with and I noticed that
* we emit SQL along the lines of
* select coalesce(max(rht2_0.RES_UPDATED), timestamp with time zone '2024-10-05 18:24:48.172000000Z')
* for this date, and all other dates are in GMT so this is an experiment. If nothing changes,
* we can roll this back to
* theCriteriaBuilder.literal(myRangeStartInclusive)
* JA 20241005
*/
ZonedDateTime rangeStart =
ZonedDateTime.ofInstant(Instant.ofEpochMilli(myRangeStartInclusive.getTime()), ZoneId.of("GMT"));
Expression myUpdatedMostRecentOrDefault =
theCriteriaBuilder.coalesce(myUpdatedMostRecent, theCriteriaBuilder.literal(myRangeStartInclusive));
theCriteriaBuilder.coalesce(myUpdatedMostRecent, theCriteriaBuilder.literal(rangeStart));
pastDateSubQuery
.select(myUpdatedMostRecentOrDefault)

View File

@ -89,7 +89,7 @@ public interface IFulltextSearchSvc {
boolean isDisabled();
ExtendedHSearchIndexData extractLuceneIndexData(
IBaseResource theResource, ResourceIndexedSearchParams theNewParams);
IBaseResource theResource, ResourceTable theEntity, ResourceIndexedSearchParams theNewParams);
/**
* Returns true if the parameter map can be handled for hibernate search.

View File

@ -164,6 +164,7 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport
@Override
public IBaseResource fetchStructureDefinition(String theUrl) {
assert myStructureDefinitionType != null;
return fetchResource(myStructureDefinitionType, theUrl);
}

View File

@ -150,6 +150,6 @@ public interface IBatch2WorkChunkRepository
@Param("status") WorkChunkStatusEnum theStatus);
@Query(
"SELECT new ca.uhn.fhir.batch2.model.BatchWorkChunkStatusDTO(e.myTargetStepId, e.myStatus, min(e.myStartTime), max(e.myEndTime), avg(e.myEndTime - e.myStartTime), count(*)) FROM Batch2WorkChunkEntity e WHERE e.myInstanceId=:instanceId GROUP BY e.myTargetStepId, e.myStatus")
"SELECT new ca.uhn.fhir.batch2.model.BatchWorkChunkStatusDTO(e.myTargetStepId, e.myStatus, min(e.myStartTime), max(e.myEndTime), avg(cast((e.myEndTime - e.myStartTime) as long)), count(*)) FROM Batch2WorkChunkEntity e WHERE e.myInstanceId=:instanceId GROUP BY e.myTargetStepId, e.myStatus")
List<BatchWorkChunkStatusDTO> fetchWorkChunkStatusForInstance(@Param("instanceId") String theInstanceId);
}

View File

@ -124,12 +124,15 @@ public class ExpungeEverythingService implements IExpungeEverythingService {
final AtomicInteger counter = new AtomicInteger();
// Notify Interceptors about pre-action call
HookParams hooks = new HookParams()
.add(AtomicInteger.class, counter)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PRESTORAGE_EXPUNGE_EVERYTHING, hooks);
IInterceptorBroadcaster compositeBroadcaster =
CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, theRequest);
if (compositeBroadcaster.hasHooks(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_EVERYTHING)) {
HookParams hooks = new HookParams()
.add(AtomicInteger.class, counter)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest);
compositeBroadcaster.callHooks(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_EVERYTHING, hooks);
}
ourLog.info("BEGINNING GLOBAL $expunge");
Propagation propagation = Propagation.REQUIRES_NEW;

View File

@ -256,8 +256,9 @@ public class JpaResourceExpungeService implements IResourceExpungeService<JpaPid
ResourceHistoryTable theVersion,
IdDt theId) {
final AtomicInteger counter = new AtomicInteger();
if (CompositeInterceptorBroadcaster.hasHooks(
Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE, myInterceptorBroadcaster, theRequestDetails)) {
IInterceptorBroadcaster compositeBroadcaster =
CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, theRequestDetails);
if (compositeBroadcaster.hasHooks(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE)) {
IBaseResource resource = myJpaStorageResourceParser.toResource(theVersion, false);
HookParams params = new HookParams()
.add(AtomicInteger.class, counter)
@ -265,8 +266,7 @@ public class JpaResourceExpungeService implements IResourceExpungeService<JpaPid
.add(IBaseResource.class, resource)
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster, theRequestDetails, Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE, params);
compositeBroadcaster.callHooks(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE, params);
}
theRemainingCount.addAndGet(-1 * counter.get());
}

View File

@ -392,7 +392,7 @@ public class ExtendedHSearchClauseBuilder {
/**
* Create date clause from date params. The date lower and upper bounds are taken
* into considertion when generating date query ranges
* into consideration when generating date query ranges
*
* <p>Example 1 ('eq' prefix/empty): <code>http://fhirserver/Observation?date=eq2020</code>
* would generate the following search clause

View File

@ -25,6 +25,8 @@ import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.TagDefinition;
import ca.uhn.fhir.jpa.model.search.CompositeSearchIndexData;
import ca.uhn.fhir.jpa.model.search.DateSearchIndexData;
import ca.uhn.fhir.jpa.model.search.ExtendedHSearchIndexData;
@ -37,6 +39,7 @@ import ca.uhn.fhir.rest.server.util.ResourceSearchParams;
import ca.uhn.fhir.util.MetaUtil;
import com.google.common.base.Strings;
import jakarta.annotation.Nonnull;
import org.apache.commons.lang3.ObjectUtils;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -74,8 +77,10 @@ public class ExtendedHSearchIndexExtractor {
}
@Nonnull
public ExtendedHSearchIndexData extract(IBaseResource theResource, ResourceIndexedSearchParams theNewParams) {
ExtendedHSearchIndexData retVal = new ExtendedHSearchIndexData(myContext, myJpaStorageSettings, theResource);
public ExtendedHSearchIndexData extract(
IBaseResource theResource, ResourceTable theEntity, ResourceIndexedSearchParams theNewParams) {
ExtendedHSearchIndexData retVal =
new ExtendedHSearchIndexData(myContext, myJpaStorageSettings, theResource, theEntity);
if (myJpaStorageSettings.isStoreResourceInHSearchIndex()) {
retVal.setRawResourceData(myContext.newJsonParser().encodeResourceToString(theResource));
@ -113,11 +118,27 @@ public class ExtendedHSearchIndexExtractor {
.filter(nextParam -> !nextParam.isMissing())
.forEach(nextParam -> retVal.addUriIndexData(nextParam.getParamName(), nextParam.getUri()));
theResource.getMeta().getTag().forEach(tag -> retVal.addTokenIndexData("_tag", tag));
theEntity.getTags().forEach(tag -> {
TagDefinition td = tag.getTag();
theResource.getMeta().getSecurity().forEach(sec -> retVal.addTokenIndexData("_security", sec));
theResource.getMeta().getProfile().forEach(prof -> retVal.addUriIndexData("_profile", prof.getValue()));
IBaseCoding coding = (IBaseCoding) myContext.getVersion().newCodingDt();
coding.setVersion(td.getVersion());
coding.setDisplay(td.getDisplay());
coding.setCode(td.getCode());
coding.setSystem(td.getSystem());
coding.setUserSelected(ObjectUtils.defaultIfNull(td.getUserSelected(), false));
switch (td.getTagType()) {
case TAG:
retVal.addTokenIndexData("_tag", coding);
break;
case PROFILE:
retVal.addUriIndexData("_profile", coding.getCode());
break;
case SECURITY_LABEL:
retVal.addTokenIndexData("_security", coding);
break;
}
});
String source = MetaUtil.getSource(myContext, theResource.getMeta());
if (isNotBlank(source)) {
@ -127,20 +148,14 @@ public class ExtendedHSearchIndexExtractor {
theNewParams.myCompositeParams.forEach(nextParam ->
retVal.addCompositeIndexData(nextParam.getSearchParamName(), buildCompositeIndexData(nextParam)));
if (theResource.getMeta().getLastUpdated() != null) {
int ordinal = ResourceIndexedSearchParamDate.calculateOrdinalValue(
theResource.getMeta().getLastUpdated())
if (theEntity.getUpdated() != null && !theEntity.getUpdated().isEmpty()) {
int ordinal = ResourceIndexedSearchParamDate.calculateOrdinalValue(theEntity.getUpdatedDate())
.intValue();
retVal.addDateIndexData(
"_lastUpdated",
theResource.getMeta().getLastUpdated(),
ordinal,
theResource.getMeta().getLastUpdated(),
ordinal);
"_lastUpdated", theEntity.getUpdatedDate(), ordinal, theEntity.getUpdatedDate(), ordinal);
}
if (!theNewParams.myLinks.isEmpty()) {
// awkwardly, links are indexed by jsonpath, not by search param.
// so we re-build the linkage.
Map<String, List<String>> linkPathToParamName = new HashMap<>();

View File

@ -28,6 +28,7 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SearchContainedModeEnum;
import ca.uhn.fhir.rest.param.CompositeParam;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
@ -89,19 +90,28 @@ public class ExtendedHSearchSearchBuilder {
* be inaccurate and wrong.
*/
public boolean canUseHibernateSearch(
String theResourceType, SearchParameterMap myParams, ISearchParamRegistry theSearchParamRegistry) {
String theResourceType, SearchParameterMap theParams, ISearchParamRegistry theSearchParamRegistry) {
boolean canUseHibernate = false;
ResourceSearchParams resourceActiveSearchParams = theSearchParamRegistry.getActiveSearchParams(
theResourceType, ISearchParamRegistry.SearchParamLookupContextEnum.SEARCH);
for (String paramName : myParams.keySet()) {
for (String paramName : theParams.keySet()) {
// special SearchParam handling:
// _lastUpdated
if (theParams.getLastUpdated() != null) {
canUseHibernate = !illegalForHibernateSearch(Constants.PARAM_LASTUPDATED, resourceActiveSearchParams);
if (!canUseHibernate) {
return false;
}
}
// is this parameter supported?
if (illegalForHibernateSearch(paramName, resourceActiveSearchParams)) {
canUseHibernate = false;
} else {
// are the parameter values supported?
canUseHibernate =
myParams.get(paramName).stream()
theParams.get(paramName).stream()
.flatMap(Collection::stream)
.collect(Collectors.toList())
.stream()
@ -136,6 +146,7 @@ public class ExtendedHSearchSearchBuilder {
// not yet supported in HSearch
myParams.getSearchContainedMode() == SearchContainedModeEnum.FALSE
&& supportsLastUpdated(myParams)
&& // ???
myParams.entrySet().stream()
.filter(e -> !ourUnsafeSearchParmeters.contains(e.getKey()))
@ -145,6 +156,19 @@ public class ExtendedHSearchSearchBuilder {
.allMatch(this::isParamTypeSupported);
}
private boolean supportsLastUpdated(SearchParameterMap theMap) {
if (theMap.getLastUpdated() == null || theMap.getLastUpdated().isEmpty()) {
return true;
}
DateRangeParam lastUpdated = theMap.getLastUpdated();
return lastUpdated.getLowerBound() != null
&& isParamTypeSupported(lastUpdated.getLowerBound())
&& lastUpdated.getUpperBound() != null
&& isParamTypeSupported(lastUpdated.getUpperBound());
}
/**
* Do we support this query param type+modifier?
* <p>

View File

@ -106,13 +106,15 @@ public class DeleteConflictService {
}
// Notify Interceptors about pre-action call
IInterceptorBroadcaster compositeBroadcaster =
CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, theRequest);
HookParams hooks = new HookParams()
.add(DeleteConflictList.class, theDeleteConflicts)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(TransactionDetails.class, theTransactionDetails);
return (DeleteConflictOutcome) CompositeInterceptorBroadcaster.doCallHooksAndReturnObject(
myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PRESTORAGE_DELETE_CONFLICTS, hooks);
return (DeleteConflictOutcome)
compositeBroadcaster.callHooksAndReturnObject(Pointcut.STORAGE_PRESTORAGE_DELETE_CONFLICTS, hooks);
}
private void addConflictsToList(

View File

@ -155,17 +155,19 @@ public class ThreadSafeResourceDeleterSvc {
TransactionDetails theTransactionDetails,
IdDt nextSource,
IFhirResourceDao<?> dao) {
// Interceptor call: STORAGE_CASCADE_DELETE
// Remove the version so we grab the latest version to delete
IBaseResource resource = dao.read(nextSource.toVersionless(), theRequest);
// Interceptor call: STORAGE_CASCADE_DELETE
IInterceptorBroadcaster compositeBroadcaster =
CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, theRequest);
HookParams params = new HookParams()
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(DeleteConflictList.class, theConflictList)
.add(IBaseResource.class, resource);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_CASCADE_DELETE, params);
compositeBroadcaster.callHooks(Pointcut.STORAGE_CASCADE_DELETE, params);
return dao.delete(resource.getIdElement(), theConflictList, theRequest, theTransactionDetails);
}

View File

@ -27,7 +27,6 @@ import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.dao.HistoryBuilder;
import ca.uhn.fhir.jpa.dao.HistoryBuilderFactory;
@ -189,15 +188,17 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
retVal.add(myJpaStorageResourceParser.toResource(resource, true));
}
IInterceptorBroadcaster compositeBroadcaster =
CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, myRequest);
// Interceptor call: STORAGE_PREACCESS_RESOURCES
{
if (compositeBroadcaster.hasHooks(Pointcut.STORAGE_PREACCESS_RESOURCES)) {
SimplePreResourceAccessDetails accessDetails = new SimplePreResourceAccessDetails(retVal);
HookParams params = new HookParams()
.add(IPreResourceAccessDetails.class, accessDetails)
.add(RequestDetails.class, myRequest)
.addIfMatchesType(ServletRequestDetails.class, myRequest);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster, myRequest, Pointcut.STORAGE_PREACCESS_RESOURCES, params);
compositeBroadcaster.callHooks(Pointcut.STORAGE_PREACCESS_RESOURCES, params);
for (int i = retVal.size() - 1; i >= 0; i--) {
if (accessDetails.isDontReturnResourceAtIndex(i)) {
@ -207,14 +208,13 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
}
// Interceptor broadcast: STORAGE_PRESHOW_RESOURCES
{
if (compositeBroadcaster.hasHooks(Pointcut.STORAGE_PRESHOW_RESOURCES)) {
SimplePreResourceShowDetails showDetails = new SimplePreResourceShowDetails(retVal);
HookParams params = new HookParams()
.add(IPreResourceShowDetails.class, showDetails)
.add(RequestDetails.class, myRequest)
.addIfMatchesType(ServletRequestDetails.class, myRequest);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster, myRequest, Pointcut.STORAGE_PRESHOW_RESOURCES, params);
compositeBroadcaster.callHooks(Pointcut.STORAGE_PRESHOW_RESOURCES, params);
retVal = showDetails.toList();
}
@ -254,9 +254,8 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
String resourceName = mySearchEntity.getResourceType();
Class<? extends IBaseResource> resourceType =
myContext.getResourceDefinition(resourceName).getImplementingClass();
IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(resourceName);
final ISearchBuilder sb = mySearchBuilderFactory.newSearchBuilder(dao, resourceName, resourceType);
final ISearchBuilder sb = mySearchBuilderFactory.newSearchBuilder(resourceName, resourceType);
RequestPartitionId requestPartitionId = getRequestPartitionId();
// we request 1 more resource than we need

View File

@ -374,8 +374,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
Class<? extends IBaseResource> resourceTypeClass =
myContext.getResourceDefinition(theResourceType).getImplementingClass();
final ISearchBuilder<JpaPid> sb =
mySearchBuilderFactory.newSearchBuilder(theCallingDao, theResourceType, resourceTypeClass);
final ISearchBuilder<JpaPid> sb = mySearchBuilderFactory.newSearchBuilder(theResourceType, resourceTypeClass);
sb.setFetchSize(mySyncSize);
final Integer loadSynchronousUpTo = getLoadSynchronousUpToOrNull(theCacheControlDirective);
@ -599,18 +598,19 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
.withRequest(theRequestDetails)
.withRequestPartitionId(theRequestPartitionId)
.execute(() -> {
IInterceptorBroadcaster compositeBroadcaster =
CompositeInterceptorBroadcaster.newCompositeBroadcaster(
myInterceptorBroadcaster, theRequestDetails);
// Interceptor call: STORAGE_PRECHECK_FOR_CACHED_SEARCH
HookParams params = new HookParams()
.add(SearchParameterMap.class, theParams)
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
Object outcome = CompositeInterceptorBroadcaster.doCallHooksAndReturnObject(
myInterceptorBroadcaster,
theRequestDetails,
Pointcut.STORAGE_PRECHECK_FOR_CACHED_SEARCH,
params);
if (Boolean.FALSE.equals(outcome)) {
boolean canUseCache =
compositeBroadcaster.callHooks(Pointcut.STORAGE_PRECHECK_FOR_CACHED_SEARCH, params);
if (!canUseCache) {
return null;
}
@ -626,11 +626,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
.add(SearchParameterMap.class, theParams)
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster,
theRequestDetails,
Pointcut.JPA_PERFTRACE_SEARCH_REUSING_CACHED,
params);
compositeBroadcaster.callHooks(Pointcut.JPA_PERFTRACE_SEARCH_REUSING_CACHED, params);
return myPersistedJpaBundleProviderFactory.newInstance(theRequestDetails, searchToUse.getUuid());
});

View File

@ -27,7 +27,6 @@ import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IResultIterator;
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
@ -181,12 +180,16 @@ public class SynchronousSearchSvcImpl implements ISynchronousSearchSvc {
}
JpaPreResourceAccessDetails accessDetails = new JpaPreResourceAccessDetails(pids, () -> theSb);
HookParams params = new HookParams()
.add(IPreResourceAccessDetails.class, accessDetails)
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster, theRequestDetails, Pointcut.STORAGE_PREACCESS_RESOURCES, params);
IInterceptorBroadcaster compositeBroadcaster =
CompositeInterceptorBroadcaster.newCompositeBroadcaster(
myInterceptorBroadcaster, theRequestDetails);
if (compositeBroadcaster.hasHooks(Pointcut.STORAGE_PREACCESS_RESOURCES)) {
HookParams params = new HookParams()
.add(IPreResourceAccessDetails.class, accessDetails)
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
compositeBroadcaster.callHooks(Pointcut.STORAGE_PREACCESS_RESOURCES, params);
}
for (int i = pids.size() - 1; i >= 0; i--) {
if (accessDetails.isDontReturnResourceAtIndex(i)) {
@ -279,12 +282,9 @@ public class SynchronousSearchSvcImpl implements ISynchronousSearchSvc {
RequestPartitionId theRequestPartitionId) {
final String searchUuid = UUID.randomUUID().toString();
IFhirResourceDao<?> callingDao = myDaoRegistry.getResourceDao(theResourceType);
Class<? extends IBaseResource> resourceTypeClass =
myContext.getResourceDefinition(theResourceType).getImplementingClass();
final ISearchBuilder sb =
mySearchBuilderFactory.newSearchBuilder(callingDao, theResourceType, resourceTypeClass);
final ISearchBuilder sb = mySearchBuilderFactory.newSearchBuilder(theResourceType, resourceTypeClass);
sb.setFetchSize(mySyncSize);
return executeQuery(
theSearchParameterMap,

View File

@ -210,7 +210,7 @@ public class QueryStack {
CoordsPredicateBuilder coordsBuilder = (CoordsPredicateBuilder) builder;
List<List<IQueryParameterType>> params = theParams.get(theParamName);
if (params.size() > 0 && params.get(0).size() > 0) {
if (!params.isEmpty() && !params.get(0).isEmpty()) {
IQueryParameterType param = params.get(0).get(0);
ParsedLocationParam location = ParsedLocationParam.from(theParams, param);
double latitudeValue = location.getLatitudeValue();
@ -2134,6 +2134,10 @@ public class QueryStack {
if (nextParam.getModifier() == TokenParamModifier.NOT) {
paramInverted = true;
}
} else if (nextOrParam instanceof ReferenceParam) {
ReferenceParam nextParam = (ReferenceParam) nextOrParam;
code = nextParam.getValue();
system = null;
} else {
UriParam nextParam = (UriParam) nextOrParam;
code = nextParam.getValue();
@ -2160,8 +2164,10 @@ public class QueryStack {
}
}
UriParam nextParam = (UriParam) nextParamUncasted;
if (isNotBlank(nextParam.getValue())) {
if (nextParamUncasted instanceof ReferenceParam
&& isNotBlank(((ReferenceParam) nextParamUncasted).getValue())) {
return true;
} else if (nextParamUncasted instanceof UriParam && isNotBlank(((UriParam) nextParamUncasted).getValue())) {
return true;
}
}

View File

@ -33,7 +33,6 @@ import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IDao;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
import ca.uhn.fhir.jpa.api.svc.ResolveIdentityMode;
@ -192,7 +191,6 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
private final FhirContext myContext;
private final IIdHelperService<JpaPid> myIdHelperService;
private final JpaStorageSettings myStorageSettings;
private final IDao myCallingDao;
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
@ -220,7 +218,6 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public SearchBuilder(
IDao theDao,
String theResourceName,
JpaStorageSettings theStorageSettings,
HapiFhirLocalContainerEntityManagerFactoryBean theEntityManagerFactory,
@ -235,7 +232,6 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
FhirContext theContext,
IIdHelperService theIdHelperService,
Class<? extends IBaseResource> theResourceType) {
myCallingDao = theDao;
myResourceName = theResourceName;
myResourceType = theResourceType;
myStorageSettings = theStorageSettings;
@ -426,15 +422,15 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
if (theSearchRuntimeDetails != null) {
theSearchRuntimeDetails.setFoundIndexMatchesCount(resultCount);
HookParams params = new HookParams()
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(SearchRuntimeDetails.class, theSearchRuntimeDetails);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster,
theRequest,
Pointcut.JPA_PERFTRACE_INDEXSEARCH_QUERY_COMPLETE,
params);
IInterceptorBroadcaster compositeBroadcaster =
CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, theRequest);
if (compositeBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_INDEXSEARCH_QUERY_COMPLETE)) {
HookParams params = new HookParams()
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(SearchRuntimeDetails.class, theSearchRuntimeDetails);
compositeBroadcaster.callHooks(Pointcut.JPA_PERFTRACE_INDEXSEARCH_QUERY_COMPLETE, params);
}
}
// can we skip the database entirely and return the pid list from here?
@ -1393,8 +1389,10 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
String searchIdOrDescription = theParameters.getSearchIdOrDescription();
List<String> desiredResourceTypes = theParameters.getDesiredResourceTypes();
boolean hasDesiredResourceTypes = desiredResourceTypes != null && !desiredResourceTypes.isEmpty();
if (CompositeInterceptorBroadcaster.hasHooks(
Pointcut.JPA_PERFTRACE_RAW_SQL, myInterceptorBroadcaster, theParameters.getRequestDetails())) {
IInterceptorBroadcaster compositeBroadcaster =
CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, request);
if (compositeBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_RAW_SQL)) {
CurrentThreadCaptureQueriesListener.startCapturing();
}
if (matches.isEmpty()) {
@ -1498,17 +1496,16 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
w.getMillisAndRestart(),
searchIdOrDescription);
if (CompositeInterceptorBroadcaster.hasHooks(
Pointcut.JPA_PERFTRACE_RAW_SQL, myInterceptorBroadcaster, request)) {
callRawSqlHookWithCurrentThreadQueries(request);
if (compositeBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_RAW_SQL)) {
callRawSqlHookWithCurrentThreadQueries(request, compositeBroadcaster);
}
// Interceptor call: STORAGE_PREACCESS_RESOURCES
// This can be used to remove results from the search result details before
// the user has a chance to know that they were in the results
if (!allAdded.isEmpty()) {
if (CompositeInterceptorBroadcaster.hasHooks(
Pointcut.STORAGE_PREACCESS_RESOURCES, myInterceptorBroadcaster, request)) {
if (compositeBroadcaster.hasHooks(Pointcut.STORAGE_PREACCESS_RESOURCES)) {
List<JpaPid> includedPidList = new ArrayList<>(allAdded);
JpaPreResourceAccessDetails accessDetails =
new JpaPreResourceAccessDetails(includedPidList, () -> this);
@ -1516,8 +1513,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
.add(IPreResourceAccessDetails.class, accessDetails)
.add(RequestDetails.class, request)
.addIfMatchesType(ServletRequestDetails.class, request);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster, request, Pointcut.STORAGE_PREACCESS_RESOURCES, params);
compositeBroadcaster.callHooks(Pointcut.STORAGE_PREACCESS_RESOURCES, params);
for (int i = includedPidList.size() - 1; i >= 0; i--) {
if (accessDetails.isDontReturnResourceAtIndex(i)) {
@ -1813,17 +1809,18 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
/**
* Calls Performance Trace Hook
* @param request the request deatils
* Sends a raw SQL query to the Pointcut for raw SQL queries.
*
* @param request the request deatils
* Sends a raw SQL query to the Pointcut for raw SQL queries.
*/
private void callRawSqlHookWithCurrentThreadQueries(RequestDetails request) {
private void callRawSqlHookWithCurrentThreadQueries(
RequestDetails request, IInterceptorBroadcaster theCompositeBroadcaster) {
SqlQueryList capturedQueries = CurrentThreadCaptureQueriesListener.getCurrentQueueAndStopCapturing();
HookParams params = new HookParams()
.add(RequestDetails.class, request)
.addIfMatchesType(ServletRequestDetails.class, request)
.add(SqlQueryList.class, capturedQueries);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster, request, Pointcut.JPA_PERFTRACE_RAW_SQL, params);
theCompositeBroadcaster.callHooks(Pointcut.JPA_PERFTRACE_RAW_SQL, params);
}
@Nullable
@ -2095,16 +2092,19 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
indexStrings.sort(Comparator.naturalOrder());
// Interceptor broadcast: JPA_PERFTRACE_INFO
String indexStringForLog = indexStrings.size() > 1 ? indexStrings.toString() : indexStrings.get(0);
StorageProcessingMessage msg = new StorageProcessingMessage()
.setMessage("Using " + theComboParam.getComboSearchParamType() + " index(es) for query for search: "
+ indexStringForLog);
HookParams params = new HookParams()
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(StorageProcessingMessage.class, msg);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_INFO, params);
IInterceptorBroadcaster compositeBroadcaster =
CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, theRequest);
if (compositeBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_INFO)) {
String indexStringForLog = indexStrings.size() > 1 ? indexStrings.toString() : indexStrings.get(0);
StorageProcessingMessage msg = new StorageProcessingMessage()
.setMessage("Using " + theComboParam.getComboSearchParamType() + " index(es) for query for search: "
+ indexStringForLog);
HookParams params = new HookParams()
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(StorageProcessingMessage.class, msg);
compositeBroadcaster.callHooks(Pointcut.JPA_PERFTRACE_INFO, params);
}
switch (requireNonNull(theComboParam.getComboSearchParamType())) {
case UNIQUE:
@ -2330,6 +2330,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
private final boolean myHavePerfTraceFoundIdHook;
private final SortSpec mySort;
private final Integer myOffset;
private final IInterceptorBroadcaster myCompositeBroadcaster;
private boolean myFirst = true;
private IncludesIterator myIncludesIterator;
/**
@ -2368,16 +2369,16 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
mySort = myParams.getSort();
myOffset = myParams.getOffset();
myRequest = theRequest;
myCompositeBroadcaster =
CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, theRequest);
// everything requires fetching recursively all related resources
if (myParams.getEverythingMode() != null) {
myFetchIncludesForEverythingOperation = true;
}
myHavePerfTraceFoundIdHook = CompositeInterceptorBroadcaster.hasHooks(
Pointcut.JPA_PERFTRACE_SEARCH_FOUND_ID, myInterceptorBroadcaster, myRequest);
myHaveRawSqlHooks = CompositeInterceptorBroadcaster.hasHooks(
Pointcut.JPA_PERFTRACE_RAW_SQL, myInterceptorBroadcaster, myRequest);
myHavePerfTraceFoundIdHook = myCompositeBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_SEARCH_FOUND_ID);
myHaveRawSqlHooks = myCompositeBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_RAW_SQL);
}
private void fetchNext() {
@ -2479,7 +2480,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
} finally {
// search finished - fire hooks
if (myHaveRawSqlHooks) {
callRawSqlHookWithCurrentThreadQueries(myRequest);
callRawSqlHookWithCurrentThreadQueries(myRequest, myCompositeBroadcaster);
}
}
@ -2488,8 +2489,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
.add(RequestDetails.class, myRequest)
.addIfMatchesType(ServletRequestDetails.class, myRequest)
.add(SearchRuntimeDetails.class, mySearchRuntimeDetails);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster, myRequest, Pointcut.JPA_PERFTRACE_SEARCH_FIRST_RESULT_LOADED, params);
myCompositeBroadcaster.callHooks(Pointcut.JPA_PERFTRACE_SEARCH_FIRST_RESULT_LOADED, params);
myFirst = false;
}
@ -2498,8 +2498,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
.add(RequestDetails.class, myRequest)
.addIfMatchesType(ServletRequestDetails.class, myRequest)
.add(SearchRuntimeDetails.class, mySearchRuntimeDetails);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster, myRequest, Pointcut.JPA_PERFTRACE_SEARCH_SELECT_COMPLETE, params);
myCompositeBroadcaster.callHooks(Pointcut.JPA_PERFTRACE_SEARCH_SELECT_COMPLETE, params);
}
}
@ -2523,8 +2522,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
HookParams params = new HookParams()
.add(Integer.class, System.identityHashCode(this))
.add(Object.class, theNextLong);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster, myRequest, Pointcut.JPA_PERFTRACE_SEARCH_FOUND_ID, params);
myCompositeBroadcaster.callHooks(Pointcut.JPA_PERFTRACE_SEARCH_FOUND_ID, params);
}
private void sendProcessingMsgAndFirePerformanceHook() {
@ -2627,14 +2625,18 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
firePerformanceMessage(theRequest, theMessage, Pointcut.JPA_PERFTRACE_WARNING);
}
private void firePerformanceMessage(RequestDetails theRequest, String theMessage, Pointcut pointcut) {
StorageProcessingMessage message = new StorageProcessingMessage();
message.setMessage(theMessage);
HookParams params = new HookParams()
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(StorageProcessingMessage.class, message);
CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, pointcut, params);
private void firePerformanceMessage(RequestDetails theRequest, String theMessage, Pointcut thePointcut) {
IInterceptorBroadcaster compositeBroadcaster =
CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, theRequest);
if (compositeBroadcaster.hasHooks(thePointcut)) {
StorageProcessingMessage message = new StorageProcessingMessage();
message.setMessage(theMessage);
HookParams params = new HookParams()
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(StorageProcessingMessage.class, message);
compositeBroadcaster.callHooks(thePointcut, params);
}
}
public static int getMaximumPageSize() {

View File

@ -53,14 +53,17 @@ public class StorageInterceptorHooksFacade {
SearchParameterMap theParams,
Search search,
RequestPartitionId theRequestPartitionId) {
HookParams params = new HookParams()
.add(ICachedSearchDetails.class, search)
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
.add(SearchParameterMap.class, theParams)
.add(RequestPartitionId.class, theRequestPartitionId);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster, theRequestDetails, Pointcut.STORAGE_PRESEARCH_REGISTERED, params);
IInterceptorBroadcaster compositeBroadcaster =
CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, theRequestDetails);
if (compositeBroadcaster.hasHooks(Pointcut.STORAGE_PRESEARCH_REGISTERED)) {
HookParams params = new HookParams()
.add(ICachedSearchDetails.class, search)
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
.add(SearchParameterMap.class, theParams)
.add(RequestPartitionId.class, theRequestPartitionId);
compositeBroadcaster.callHooks(Pointcut.STORAGE_PRESEARCH_REGISTERED, params);
}
}
// private IInterceptorBroadcaster myInterceptorBroadcaster;
}

View File

@ -407,12 +407,16 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder im
}
String message = builder.toString();
StorageProcessingMessage msg = new StorageProcessingMessage().setMessage(message);
HookParams params = new HookParams()
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(StorageProcessingMessage.class, msg);
CompositeInterceptorBroadcaster.doCallHooks(
myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_WARNING, params);
IInterceptorBroadcaster compositeBroadcaster =
CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, theRequest);
if (compositeBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_WARNING)) {
HookParams params = new HookParams()
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(StorageProcessingMessage.class, msg);
compositeBroadcaster.callHooks(Pointcut.JPA_PERFTRACE_WARNING, params);
}
}
/**

Some files were not shown because too many files have changed in this diff Show More