Merge branch 'master' into batch-empi-job

This commit is contained in:
Tadgh 2020-07-16 11:00:32 -07:00
commit 6bb92717f5
132 changed files with 3458 additions and 1475 deletions

View File

@ -25,16 +25,6 @@ jobs:
inputs: inputs:
targetType: 'inline' targetType: 'inline'
script: mkdir -p $(MAVEN_CACHE_FOLDER); pwd; ls -al $(MAVEN_CACHE_FOLDER) script: mkdir -p $(MAVEN_CACHE_FOLDER); pwd; ls -al $(MAVEN_CACHE_FOLDER)
# - task: Maven@3
#env:
# JAVA_HOME_11_X64: /usr/local/openjdk-11
# inputs:
# goals: 'dependency:go-offline'
# # These are Maven CLI options (and show up in the build logs) - "-nsu"=Don't update snapshots. We can remove this when Maven OSS is more healthy
# options: '-P ALLMODULES,JACOCO,CI,ERRORPRONE -e -B -Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)'
# # These are JVM options (and don't show up in the build logs)
# mavenOptions: '-Xmx1024m $(MAVEN_OPTS) -Dorg.slf4j.simpleLogger.showDateTime=true -Dorg.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss,SSS -Duser.timezone=America/Toronto'
# jdkVersionOption: 1.11
- task: Maven@3 - task: Maven@3
env: env:
JAVA_HOME_11_X64: /usr/local/openjdk-11 JAVA_HOME_11_X64: /usr/local/openjdk-11

View File

@ -22,9 +22,12 @@ package ca.uhn.fhir.context.phonetic;
import org.apache.commons.codec.EncoderException; import org.apache.commons.codec.EncoderException;
import org.apache.commons.codec.StringEncoder; import org.apache.commons.codec.StringEncoder;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.StringJoiner;
public class ApacheEncoder implements IPhoneticEncoder { public class ApacheEncoder implements IPhoneticEncoder {
private static final Logger ourLog = LoggerFactory.getLogger(ApacheEncoder.class); private static final Logger ourLog = LoggerFactory.getLogger(ApacheEncoder.class);
@ -44,10 +47,44 @@ public class ApacheEncoder implements IPhoneticEncoder {
@Override @Override
public String encode(String theString) { public String encode(String theString) {
try { try {
// If the string contains a space, encode alpha parts separately so, for example, numbers are preserved in address lines.
if (theString.contains(" ")) {
return encodeStringWithSpaces(theString);
}
return myStringEncoder.encode(theString); return myStringEncoder.encode(theString);
} catch (EncoderException e) { } catch (EncoderException e) {
ourLog.error("Failed to encode string " + theString, e); ourLog.error("Failed to encode string " + theString, e);
return theString; return theString;
} }
} }
private String encodeStringWithSpaces(String theString) throws EncoderException {
StringJoiner joiner = new StringJoiner(" ");
// This sub-stack holds the alpha parts
StringJoiner alphaJoiner = new StringJoiner(" ");
for (String part : theString.split("[\\s\\W]+")) {
if (StringUtils.isAlpha(part)) {
alphaJoiner.add(part);
} else {
// Once we hit a non-alpha part, encode all the alpha parts together as a single string
// This is to allow encoders like METAPHONE to match Hans Peter to Hanspeter
alphaJoiner = encodeAlphaParts(joiner, alphaJoiner);
joiner.add(part);
}
}
encodeAlphaParts(joiner, alphaJoiner);
return joiner.toString();
}
private StringJoiner encodeAlphaParts(StringJoiner theJoiner, StringJoiner theAlphaJoiner) throws EncoderException {
// Encode the alpha parts as a single string and then flush the alpha encoder
if (theAlphaJoiner.length() > 0) {
theJoiner.add(myStringEncoder.encode(theAlphaJoiner.toString()));
theAlphaJoiner = new StringJoiner(" ");
}
return theAlphaJoiner;
}
} }

View File

@ -25,6 +25,9 @@ import org.apache.commons.lang3.builder.ToStringStyle;
public class ConceptValidationOptions { public class ConceptValidationOptions {
private boolean myValidateDisplay;
private boolean myInferSystem;
public boolean isInferSystem() { public boolean isInferSystem() {
return myInferSystem; return myInferSystem;
} }
@ -34,12 +37,19 @@ public class ConceptValidationOptions {
return this; return this;
} }
private boolean myInferSystem;
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("inferSystem", myInferSystem) .append("inferSystem", myInferSystem)
.toString(); .toString();
} }
public boolean isValidateDisplay() {
return myValidateDisplay;
}
public ConceptValidationOptions setValidateDisplay(boolean theValidateDisplay) {
myValidateDisplay = theValidateDisplay;
return this;
}
} }

View File

@ -20,6 +20,8 @@ package ca.uhn.fhir.context.support;
* #L% * #L%
*/ */
import org.thymeleaf.util.Validate;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -29,6 +31,7 @@ public class ValidationSupportContext {
private final Set<String> myCurrentlyGeneratingSnapshots = new HashSet<>(); private final Set<String> myCurrentlyGeneratingSnapshots = new HashSet<>();
public ValidationSupportContext(IValidationSupport theRootValidationSupport) { public ValidationSupportContext(IValidationSupport theRootValidationSupport) {
Validate.notNull(theRootValidationSupport, "theRootValidationSupport musty not be null");
myRootValidationSupport = theRootValidationSupport; myRootValidationSupport = theRootValidationSupport;
} }

View File

@ -854,6 +854,21 @@ public enum Pointcut {
*/ */
SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_REGISTERED(void.class, "ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription"), SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_REGISTERED(void.class, "ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription"),
/**
* <b>Subscription Hook:</b>
* Invoked immediately after an active subscription is "registered". In HAPI FHIR, when
* a subscription
* <p>
* Hooks may make changes to the canonicalized subscription and this will have an effect
* on processing across this server. Note however that timing issues may occur, since the
* subscription is already technically live by the time this hook is called.
* </p>
* No parameters are currently supported.
* <p>
* Hooks should return <code>void</code>.
* </p>
*/
SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_UNREGISTERED(void.class),
/** /**
* <b>Storage Hook:</b> * <b>Storage Hook:</b>
@ -1562,6 +1577,42 @@ public enum Pointcut {
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails" "ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
), ),
/**
* <b>Storage Hook:</b>
* Invoked when a transaction has been rolled back as a result of a {@link ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException},
* meaning that a database constraint has been violated. This pointcut allows an interceptor to specify a resolution strategy
* other than simply returning the error to the client. This interceptor will be fired after the database transaction rollback
* has been completed.
* <p>
* Hooks may accept the following parameters:
* </p>
* <ul>
* <li>
* ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the servlet request. Note that the bean
* properties are not all guaranteed to be populated, depending on how early during processing the
* exception occurred. <b>Note that this parameter may be null in contexts where the request is not
* known, such as while processing searches</b>
* </li>
* <li>
* ca.uhn.fhir.rest.server.servlet.ServletRequestDetails - A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the servlet request. This parameter is identical to the RequestDetails parameter above but will
* only be populated when operating in a RestfulServer implementation. It is provided as a convenience.
* </li>
* </ul>
* <p>
* Hooks should return <code>ca.uhn.fhir.jpa.api.model.ResourceVersionConflictResolutionStrategy</code>. Hooks should not
* throw any exception.
* </p>
*/
STORAGE_VERSION_CONFLICT(
"ca.uhn.fhir.jpa.api.model.ResourceVersionConflictResolutionStrategy",
"ca.uhn.fhir.rest.api.server.RequestDetails",
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
),
/** /**
* <b>EMPI Hook:</b> * <b>EMPI Hook:</b>
* Invoked whenever a persisted Patient/Practitioner resource (a resource that has just been stored in the * Invoked whenever a persisted Patient/Practitioner resource (a resource that has just been stored in the

View File

@ -20,6 +20,9 @@ package ca.uhn.fhir.rest.annotation;
* #L% * #L%
*/ */
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@ -32,4 +35,5 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(value= ElementType.METHOD) @Target(value= ElementType.METHOD)
public @interface GraphQL { public @interface GraphQL {
RequestTypeEnum type() default RequestTypeEnum.GET;
} }

View File

@ -37,6 +37,6 @@ import java.lang.annotation.Target;
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER) @Target(ElementType.PARAMETER)
public @interface GraphQLQuery { public @interface GraphQLQueryBody {
// ignore // ignore
} }

View File

@ -0,0 +1,42 @@
package ca.uhn.fhir.rest.annotation;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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 LBoicense.
* #L%
*/
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation should be placed on the parameter of a
* {@link GraphQL @GraphQL} annotated method. The given
* parameter will be populated with the specific graphQL
* query being requested.
*
* <p>
* This parameter should be of type {@link String}
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface GraphQLQueryUrl {
// ignore
}

View File

@ -66,6 +66,7 @@ public class Constants {
public static final String CT_HTML = "text/html"; public static final String CT_HTML = "text/html";
public static final String CT_HTML_WITH_UTF8 = "text/html" + CHARSET_UTF8_CTSUFFIX; public static final String CT_HTML_WITH_UTF8 = "text/html" + CHARSET_UTF8_CTSUFFIX;
public static final String CT_JSON = "application/json"; public static final String CT_JSON = "application/json";
public static final String CT_GRAPHQL = "application/graphql";
public static final String CT_JSON_PATCH = "application/json-patch+json"; public static final String CT_JSON_PATCH = "application/json-patch+json";
public static final String CT_OCTET_STREAM = "application/octet-stream"; public static final String CT_OCTET_STREAM = "application/octet-stream";
public static final String CT_TEXT = "text/plain"; public static final String CT_TEXT = "text/plain";

View File

@ -37,6 +37,9 @@ public class ResourceVersionConflictException extends BaseServerResponseExceptio
public static final int STATUS_CODE = Constants.STATUS_HTTP_409_CONFLICT; public static final int STATUS_CODE = Constants.STATUS_HTTP_409_CONFLICT;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/**
* Constructor
*/
public ResourceVersionConflictException(String error) { public ResourceVersionConflictException(String error) {
super(STATUS_CODE, error); super(STATUS_CODE, error);
} }

View File

@ -13,6 +13,8 @@ import ca.uhn.fhir.context.RuntimeExtensionDtDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.model.api.ExtensionDt; import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.IIdentifiableElement;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
import ca.uhn.fhir.model.base.composite.BaseContainedDt; import ca.uhn.fhir.model.base.composite.BaseContainedDt;
@ -21,6 +23,7 @@ import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseElement;
import org.hl7.fhir.instance.model.api.IBaseExtension; import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseHasExtensions; import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions; import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions;
@ -128,6 +131,28 @@ public class FhirTerser {
Validate.notNull(theSource, "theSource must not be null"); Validate.notNull(theSource, "theSource must not be null");
Validate.notNull(theTarget, "theTarget must not be null"); Validate.notNull(theTarget, "theTarget must not be null");
// DSTU3+
if (theSource instanceof IBaseElement) {
IBaseElement source = (IBaseElement) theSource;
IBaseElement target = (IBaseElement) theTarget;
target.setId(source.getId());
}
// DSTU2 only
if (theSource instanceof IIdentifiableElement) {
IIdentifiableElement source = (IIdentifiableElement) theSource;
IIdentifiableElement target = (IIdentifiableElement) theTarget;
target.setElementSpecificId(source.getElementSpecificId());
}
// DSTU2 only
if (theSource instanceof IResource) {
IResource source = (IResource) theSource;
IResource target = (IResource) theTarget;
target.setId(source.getId());
target.getResourceMetadata().putAll(source.getResourceMetadata());
}
if (theSource instanceof IPrimitiveType<?>) { if (theSource instanceof IPrimitiveType<?>) {
if (theTarget instanceof IPrimitiveType<?>) { if (theTarget instanceof IPrimitiveType<?>) {
((IPrimitiveType<?>) theTarget).setValueAsString(((IPrimitiveType<?>) theSource).getValueAsString()); ((IPrimitiveType<?>) theTarget).setValueAsString(((IPrimitiveType<?>) theSource).getValueAsString());
@ -159,7 +184,13 @@ public class FhirTerser {
} }
BaseRuntimeElementDefinition<?> element = myContext.getElementDefinition(nextValue.getClass()); BaseRuntimeElementDefinition<?> element = myContext.getElementDefinition(nextValue.getClass());
IBase target = element.newInstance(); Object instanceConstructorArg = targetChild.getInstanceConstructorArguments();
IBase target;
if (instanceConstructorArg != null) {
target = element.newInstance(instanceConstructorArg);
} else {
target = element.newInstance();
}
targetChild.getMutator().addValue(theTarget, target); targetChild.getMutator().addValue(theTarget, target);
cloneInto(nextValue, target, theIgnoreMissingFields); cloneInto(nextValue, target, theIgnoreMissingFields);

View File

@ -0,0 +1,29 @@
package ca.uhn.fhir.context.phonetic;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.hamcrest.Matchers.startsWith;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.endsWith;
class PhoneticEncoderTest {
private static final Logger ourLog = LoggerFactory.getLogger(PhoneticEncoderTest.class);
private static final String NUMBER = "123";
private static final String STREET = "Nohili St, Suite";
private static final String SUITE = "456";
private static final String ADDRESS_LINE = NUMBER + " " + STREET + " " + SUITE;
@ParameterizedTest
@EnumSource(PhoneticEncoderEnum.class)
public void testEncodeAddress(PhoneticEncoderEnum thePhoneticEncoderEnum) {
String encoded = thePhoneticEncoderEnum.getPhoneticEncoder().encode(ADDRESS_LINE);
ourLog.info("{}: {}", thePhoneticEncoderEnum.name(), encoded);
assertThat(encoded, startsWith(NUMBER + " "));
assertThat(encoded, endsWith(" " + SUITE));
}
}

View File

@ -134,14 +134,16 @@ public class CreatePackageCommand extends BaseCommand {
} }
String[] dependencies = theCommandLine.getOptionValues(DEPENDENCY_OPT); String[] dependencies = theCommandLine.getOptionValues(DEPENDENCY_OPT);
for (String nextDependencyString : dependencies) { if (dependencies != null) {
int colonIdx = nextDependencyString.indexOf(":"); for (String nextDependencyString : dependencies) {
if (colonIdx == -1) { int colonIdx = nextDependencyString.indexOf(":");
throw new ParseException("Invalid dependency spec: " + nextDependencyString); if (colonIdx == -1) {
throw new ParseException("Invalid dependency spec: " + nextDependencyString);
}
String depName = nextDependencyString.substring(0, colonIdx);
String depVersion = nextDependencyString.substring(colonIdx + 1);
manifestGenerator.dependency(depName, depVersion);
} }
String depName = nextDependencyString.substring(0, colonIdx);
String depVersion = nextDependencyString.substring(colonIdx+1);
manifestGenerator.dependency(depName, depVersion);
} }
myWorkDirectory = Files.createTempDir(); myWorkDirectory = Files.createTempDir();

View File

@ -113,6 +113,54 @@ public class CreatePackageCommandTest extends BaseTest {
} }
@Test
public void testCreatePackage_NoDependencies() throws IOException {
StructureDefinition sd = new StructureDefinition();
sd.setUrl("http://foo/1");
writeFile(sd, "foo1.json");
ValueSet vs = new ValueSet();
vs.setUrl("http://foo/2");
writeFile(vs, "foo2.json");
App.main(new String[]{
"create-package",
"--fhir-version", "R4",
"--name", "com.example.ig",
"--version", "1.0.1",
"--include-package", myWorkDirectory.getAbsolutePath() + "/*.json",
"--target-directory", myTargetDirectory.getAbsolutePath()
});
Archiver archiver = ArchiverFactory.createArchiver("tar", "gz");
File igArchive = new File(myTargetDirectory, "com.example.ig-1.0.1.tgz");
archiver.extract(igArchive, myExtractDirectory);
List<String> allFiles = FileUtils.listFiles(myExtractDirectory, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE)
.stream()
.map(t -> t.getPath())
.sorted()
.collect(Collectors.toList());
ourLog.info("Archive contains files:\n * {}", allFiles.stream().collect(Collectors.joining("\n * ")));
// Verify package.json
String packageJsonContents = IOUtils.toString(new FileInputStream(new File(myExtractDirectory, "package/package.json")), Charsets.UTF_8);
ourLog.info("Package.json:\n{}", packageJsonContents);
String expectedPackageJson = "{\n" +
" \"name\": \"com.example.ig\",\n" +
" \"version\": \"1.0.1\"\n" +
"}";
assertEquals(expectedPackageJson, packageJsonContents);
// Try parsing the module again to make sure we can
NpmPackage loadedPackage = NpmPackage.fromPackage(new FileInputStream(igArchive));
assertEquals("com.example.ig", loadedPackage.name());
}
public void writeFile(IBaseResource theResource, String theFileName) throws IOException { public void writeFile(IBaseResource theResource, String theFileName) throws IOException {
try (FileWriter w = new FileWriter(new File(myWorkDirectory, theFileName), false)) { try (FileWriter w = new FileWriter(new File(myWorkDirectory, theFileName), false)) {
myContext.newJsonParser().encodeResourceToWriter(theResource, w); myContext.newJsonParser().encodeResourceToWriter(theResource, w);

View File

@ -53,9 +53,9 @@ public class ServerOperations {
ourLog.info("Received call with content type {} and {} bytes", contentType, bytes.length); ourLog.info("Received call with content type {} and {} bytes", contentType, bytes.length);
theServletResponse.setContentType(contentType); theServletResponse.setContentType("text/plain");
theServletResponse.getOutputStream().write(bytes); theServletResponse.getWriter().write("hello");
theServletResponse.getOutputStream().close(); theServletResponse.getWriter().close();
} }
//END SNIPPET: manualInputAndOutput //END SNIPPET: manualInputAndOutput

View File

@ -0,0 +1,5 @@
---
type: add
issue: 1896
title: "Support has been added for GraphQL querying using an HTTP POST (with the query in the body). Thanks to
Ibrohim Kholilul Islam for the pull request implementing this new feature!"

View File

@ -0,0 +1,5 @@
---
type: add
issue: 1966
title: "The GraphQL module can now accept arrays of arguments as input to searches, and will treat them as
OR'ed parameters. Thanks to Ibrohim Kholilul Isla for the pull request!"

View File

@ -0,0 +1,6 @@
---
type: add
issue: 1971
title: "A new interceptor called `UserRequestRetryVersionConflictsInterceptor` has been added to the JPA server. This interceptor
allows clients to instruct the server to attempt to avoid returning an HTTP 409 (Version Conflict) if two concurrent client
requests try to update the same resource at the same time."

View File

@ -0,0 +1,4 @@
---
type: fix
issue: 1971
title: The create-package CLI command failed with a NPE if no package dependencies were specified. This has been corrected.

View File

@ -0,0 +1,7 @@
---
type: add
issue: 1982
title: The validator will now accept codes that are defined in a ValueSet where the valueset contains an enumeration of
codes, and the CodeSystem URL refers to an unknown CodeSystem. This allows successful validation of ValueSets in several
IGs that rely on the existence of grammar based systems.

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 1983
title: "ConceptMap resources were blocked from uploading into the JPA server if the ConceptMap had a source
and/or target URL defined at the ConceptMap level but not at the group level. This prevented some US Core
resources from being successfully uploaded. This has been corrected."

View File

@ -178,3 +178,10 @@ The ResponseSizeCapturingInterceptor can be used to capture the number of charac
* [ResponseSizeCapturingInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptor.html) * [ResponseSizeCapturingInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptor.html)
* [ResponseSizeCapturingInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptor.java) * [ResponseSizeCapturingInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptor.java)
# JPA Server: Allow Cascading Deletes
The CascadingDeleteInterceptor allows clients to request deletes be cascaded to other resources that contain incoming references. See [Cascading Deletes](/docs/server_jpa/configuration.html#cascading-deletes) for more information.
# JPA Server: Retry on Version Conflicts
The UserRequestRetryVersionConflictsInterceptor allows clients to request that the server avoid version conflicts (HTTP 409) when two concurrent client requests attempt to modify the same resource. See [Version Conflicts](/docs/server_jpa/configuration.html#retry-on-version-conflict) for more information.

View File

@ -103,10 +103,26 @@ Cache-Control: no-store, max-results=20
* [This page](https://www.openhealthhub.org/t/hapi-terminology-server-uk-snomed-ct-import/592) has information on loading national editions (UK specifically) of SNOMED CT files into the database. * [This page](https://www.openhealthhub.org/t/hapi-terminology-server-uk-snomed-ct-import/592) has information on loading national editions (UK specifically) of SNOMED CT files into the database.
<a name="cascading-deletes"/>
# Cascading Deletes # Cascading Deletes
An interceptor called `CascadingDeleteInterceptor` may be registered against the Server. When this interceptor is enabled, cascading deletes may be performed using either of the following: An interceptor called `CascadingDeleteInterceptor` may be registered against the server. When this interceptor is enabled, cascading deletes may be performed using either of the following:
* The request may include the following parameter: `_cascade=delete` * The request may include the following parameter: `_cascade=delete`
* The request may include the following header: `X-Cascade: delete` * The request may include the following header: `X-Cascade: delete`
<a name="retry-on-version-conflict"/>
# Version Conflicts
If a server is serving multiple concurrent requests against the same resource, a [ResourceVersionConflictException](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/rest/server/exceptions/ResourceVersionConflictException.html) may be thrown (resulting in an **HTTP 409 Version Conflict** being returned to the client). For example, if two client requests attempt to update the same resource at the exact same time, this exception will be thrown for one of the requests. This exception is not a bug in the server itself, but instead is a defense against client updates accidentally being lost because of concurrency issues. When this occurs, it is important to consider what the root cause might be, since concurrent writes against the same resource are often indicative of a deeper application design issue.
An interceptor called `UserRequestRetryVersionConflictsInterceptor` may be registered against the server. When this interceptor is enabled, requests may include an optional header requesting for the server to try to avoid returning an error due to concurrent writes. The server will then try to avoid version conflict errors by automatically retrying requests that would have otherwise failed due to a version conflict.
With this interceptor in place, the following header can be added to individual HTTP requests to instruct the server to avoid version conflict errors:
```http
X-Retry-On-Version-Conflict: retry; max-retries=100
```

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.api.dao;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
@ -41,31 +42,6 @@ public interface IFhirResourceDaoValueSet<T extends IBaseResource, CD, CC> exten
void purgeCaches(); void purgeCaches();
ValidateCodeResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, CD theCoding, CC theCodeableConcept, RequestDetails theRequestDetails); IValidationSupport.CodeValidationResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, CD theCoding, CC theCodeableConcept, RequestDetails theRequestDetails);
class ValidateCodeResult {
private String myDisplay;
private String myMessage;
private boolean myResult;
public ValidateCodeResult(boolean theResult, String theMessage, String theDisplay) {
super();
myResult = theResult;
myMessage = theMessage;
myDisplay = theDisplay;
}
public String getDisplay() {
return myDisplay;
}
public String getMessage() {
return myMessage;
}
public boolean isResult() {
return myResult;
}
}
} }

View File

@ -0,0 +1,49 @@
package ca.uhn.fhir.jpa.api.model;
/*-
* #%L
* HAPI FHIR JPA API
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import org.apache.commons.lang3.Validate;
/**
* @since 5.1.0
*/
public class ResourceVersionConflictResolutionStrategy {
private int myMaxRetries;
private boolean myRetry;
public int getMaxRetries() {
return myMaxRetries;
}
public void setMaxRetries(int theMaxRetries) {
Validate.isTrue(theMaxRetries >= 0, "theRetryUpToMillis must not be negative");
myMaxRetries = theMaxRetries;
}
public boolean isRetry() {
return myRetry;
}
public void setRetry(boolean theRetry) {
myRetry = theRetry;
}
}

View File

@ -163,8 +163,6 @@
</exclusions> </exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.ttddyy</groupId> <groupId>net.ttddyy</groupId>
<artifactId>datasource-proxy</artifactId> <artifactId>datasource-proxy</artifactId>
@ -734,7 +732,8 @@
<version>dstu2</version> <version>dstu2</version>
<configPackageBase>ca.uhn.fhir.jpa.config</configPackageBase> <configPackageBase>ca.uhn.fhir.jpa.config</configPackageBase>
<packageBase>ca.uhn.fhir.jpa.rp.dstu2</packageBase> <packageBase>ca.uhn.fhir.jpa.rp.dstu2</packageBase>
<targetResourceSpringBeansFile>hapi-fhir-server-resourceproviders-dstu2.xml</targetResourceSpringBeansFile> <targetResourceSpringBeansFile>hapi-fhir-server-resourceproviders-dstu2.xml
</targetResourceSpringBeansFile>
<baseResourceNames/> <baseResourceNames/>
<excludeResourceNames> <excludeResourceNames>
<!-- <excludeResourceName>OperationDefinition</excludeResourceName> <excludeResourceName>OperationOutcome</excludeResourceName> --> <!-- <excludeResourceName>OperationDefinition</excludeResourceName> <excludeResourceName>OperationOutcome</excludeResourceName> -->
@ -750,7 +749,8 @@
<version>dstu3</version> <version>dstu3</version>
<configPackageBase>ca.uhn.fhir.jpa.config</configPackageBase> <configPackageBase>ca.uhn.fhir.jpa.config</configPackageBase>
<packageBase>ca.uhn.fhir.jpa.rp.dstu3</packageBase> <packageBase>ca.uhn.fhir.jpa.rp.dstu3</packageBase>
<targetResourceSpringBeansFile>hapi-fhir-server-resourceproviders-dstu3.xml</targetResourceSpringBeansFile> <targetResourceSpringBeansFile>hapi-fhir-server-resourceproviders-dstu3.xml
</targetResourceSpringBeansFile>
<baseResourceNames></baseResourceNames> <baseResourceNames></baseResourceNames>
<excludeResourceNames> <excludeResourceNames>
</excludeResourceNames> </excludeResourceNames>
@ -765,7 +765,8 @@
<version>r4</version> <version>r4</version>
<configPackageBase>ca.uhn.fhir.jpa.config</configPackageBase> <configPackageBase>ca.uhn.fhir.jpa.config</configPackageBase>
<packageBase>ca.uhn.fhir.jpa.rp.r4</packageBase> <packageBase>ca.uhn.fhir.jpa.rp.r4</packageBase>
<targetResourceSpringBeansFile>hapi-fhir-server-resourceproviders-r4.xml</targetResourceSpringBeansFile> <targetResourceSpringBeansFile>hapi-fhir-server-resourceproviders-r4.xml
</targetResourceSpringBeansFile>
<baseResourceNames></baseResourceNames> <baseResourceNames></baseResourceNames>
<excludeResourceNames> <excludeResourceNames>
</excludeResourceNames> </excludeResourceNames>
@ -780,7 +781,8 @@
<version>r5</version> <version>r5</version>
<configPackageBase>ca.uhn.fhir.jpa.config</configPackageBase> <configPackageBase>ca.uhn.fhir.jpa.config</configPackageBase>
<packageBase>ca.uhn.fhir.jpa.rp.r5</packageBase> <packageBase>ca.uhn.fhir.jpa.rp.r5</packageBase>
<targetResourceSpringBeansFile>hapi-fhir-server-resourceproviders-r5.xml</targetResourceSpringBeansFile> <targetResourceSpringBeansFile>hapi-fhir-server-resourceproviders-r5.xml
</targetResourceSpringBeansFile>
<baseResourceNames></baseResourceNames> <baseResourceNames></baseResourceNames>
</configuration> </configuration>
</execution> </execution>

View File

@ -28,6 +28,8 @@ import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersInvalidException; import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.JobParametersValidator; import org.springframework.batch.core.JobParametersValidator;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import java.util.Arrays; import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
@ -38,6 +40,8 @@ import java.util.Optional;
public class BulkExportJobParameterValidator implements JobParametersValidator { public class BulkExportJobParameterValidator implements JobParametersValidator {
@Autowired @Autowired
private IBulkExportJobDao myBulkExportJobDao; private IBulkExportJobDao myBulkExportJobDao;
@Autowired
private PlatformTransactionManager myTransactionManager;
@Override @Override
public void validate(JobParameters theJobParameters) throws JobParametersInvalidException { public void validate(JobParameters theJobParameters) throws JobParametersInvalidException {
@ -45,41 +49,45 @@ public class BulkExportJobParameterValidator implements JobParametersValidator {
throw new JobParametersInvalidException("This job needs Parameters: [readChunkSize], [jobUUID], [filters], [outputFormat], [resourceTypes]"); throw new JobParametersInvalidException("This job needs Parameters: [readChunkSize], [jobUUID], [filters], [outputFormat], [resourceTypes]");
} }
StringBuilder errorBuilder = new StringBuilder(); TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
Long readChunkSize = theJobParameters.getLong("readChunkSize"); String errorMessage = txTemplate.execute(tx -> {
if (readChunkSize == null || readChunkSize < 1) { StringBuilder errorBuilder = new StringBuilder();
errorBuilder.append("There must be a valid number for readChunkSize, which is at least 1. "); Long readChunkSize = theJobParameters.getLong("readChunkSize");
} if (readChunkSize == null || readChunkSize < 1) {
String jobUUID = theJobParameters.getString("jobUUID"); errorBuilder.append("There must be a valid number for readChunkSize, which is at least 1. ");
Optional<BulkExportJobEntity> oJob = myBulkExportJobDao.findByJobId(jobUUID);
if (!StringUtils.isBlank(jobUUID) && !oJob.isPresent()) {
errorBuilder.append("There is no persisted job that exists with UUID: " + jobUUID + ". ");
}
boolean hasExistingJob = oJob.isPresent();
//Check for to-be-created parameters.
if (!hasExistingJob) {
String resourceTypes = theJobParameters.getString("resourceTypes");
if (StringUtils.isBlank(resourceTypes)) {
errorBuilder.append("You must include [resourceTypes] as a Job Parameter");
} else {
String[] resourceArray = resourceTypes.split(",");
Arrays.stream(resourceArray).filter(resourceType -> resourceType.equalsIgnoreCase("Binary"))
.findFirst()
.ifPresent(resourceType -> {
errorBuilder.append("Bulk export of Binary resources is forbidden");
});
} }
String jobUUID = theJobParameters.getString("jobUUID");
String outputFormat = theJobParameters.getString("outputFormat"); Optional<BulkExportJobEntity> oJob = myBulkExportJobDao.findByJobId(jobUUID);
if (!StringUtils.isBlank(outputFormat) && !Constants.CT_FHIR_NDJSON.equals(outputFormat)) { if (!StringUtils.isBlank(jobUUID) && !oJob.isPresent()) {
errorBuilder.append("The only allowed format for Bulk Export is currently " + Constants.CT_FHIR_NDJSON); errorBuilder.append("There is no persisted job that exists with UUID: " + jobUUID + ". ");
} }
} boolean hasExistingJob = oJob.isPresent();
String errorMessage = errorBuilder.toString(); //Check for to-be-created parameters.
if (!hasExistingJob) {
String resourceTypes = theJobParameters.getString("resourceTypes");
if (StringUtils.isBlank(resourceTypes)) {
errorBuilder.append("You must include [resourceTypes] as a Job Parameter");
} else {
String[] resourceArray = resourceTypes.split(",");
Arrays.stream(resourceArray).filter(resourceType -> resourceType.equalsIgnoreCase("Binary"))
.findFirst()
.ifPresent(resourceType -> {
errorBuilder.append("Bulk export of Binary resources is forbidden");
});
}
String outputFormat = theJobParameters.getString("outputFormat");
if (!StringUtils.isBlank(outputFormat) && !Constants.CT_FHIR_NDJSON.equals(outputFormat)) {
errorBuilder.append("The only allowed format for Bulk Export is currently " + Constants.CT_FHIR_NDJSON);
}
}
return errorBuilder.toString();
});
if (!StringUtils.isEmpty(errorMessage)) { if (!StringUtils.isEmpty(errorMessage)) {
throw new JobParametersInvalidException(errorMessage); throw new JobParametersInvalidException(errorMessage);
} }

View File

@ -21,6 +21,7 @@ import ca.uhn.fhir.jpa.dao.ISearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory; import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
import ca.uhn.fhir.jpa.dao.index.DaoResourceLinkResolver; import ca.uhn.fhir.jpa.dao.index.DaoResourceLinkResolver;
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.graphql.JpaStorageServices; import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices; import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
@ -304,6 +305,11 @@ public abstract class BaseConfig {
return new PersistenceExceptionTranslationPostProcessor(); return new PersistenceExceptionTranslationPostProcessor();
} }
@Bean
public HapiTransactionService hapiTransactionService() {
return new HapiTransactionService();
}
@Bean @Bean
public IInterceptorService jpaInterceptorService() { public IInterceptorService jpaInterceptorService() {
return new InterceptorService(); return new InterceptorService();

View File

@ -26,7 +26,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.StaleStateException; import org.hibernate.PessimisticLockException;
import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.ConstraintViolationException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -35,6 +35,7 @@ import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
import javax.persistence.PersistenceException; import javax.persistence.PersistenceException;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class HapiFhirHibernateJpaDialect extends HibernateJpaDialect { public class HapiFhirHibernateJpaDialect extends HibernateJpaDialect {
@ -76,13 +77,14 @@ public class HapiFhirHibernateJpaDialect extends HibernateJpaDialect {
* will return it as lowercase even though the definition is in caps. * will return it as lowercase even though the definition is in caps.
*/ */
if (isNotBlank(constraintName)) { if (isNotBlank(constraintName)) {
if (constraintName.toUpperCase().contains(ResourceHistoryTable.IDX_RESVER_ID_VER)) { constraintName = constraintName.toUpperCase();
if (constraintName.contains(ResourceHistoryTable.IDX_RESVER_ID_VER)) {
throw new ResourceVersionConflictException(messageToPrepend + myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "resourceVersionConstraintFailure")); throw new ResourceVersionConflictException(messageToPrepend + myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "resourceVersionConstraintFailure"));
} }
if (constraintName.toUpperCase().contains(ResourceIndexedCompositeStringUnique.IDX_IDXCMPSTRUNIQ_STRING)) { if (constraintName.contains(ResourceIndexedCompositeStringUnique.IDX_IDXCMPSTRUNIQ_STRING)) {
throw new ResourceVersionConflictException(messageToPrepend + myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "resourceIndexedCompositeStringUniqueConstraintFailure")); throw new ResourceVersionConflictException(messageToPrepend + myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "resourceIndexedCompositeStringUniqueConstraintFailure"));
} }
if (constraintName.toUpperCase().contains(ForcedId.IDX_FORCEDID_TYPE_FID)) { if (constraintName.contains(ForcedId.IDX_FORCEDID_TYPE_FID)) {
throw new ResourceVersionConflictException(messageToPrepend + myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "forcedIdConstraintFailure")); throw new ResourceVersionConflictException(messageToPrepend + myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "forcedIdConstraintFailure"));
} }
} }
@ -102,10 +104,18 @@ public class HapiFhirHibernateJpaDialect extends HibernateJpaDialect {
* class in a method called "checkBatched" currently. This can all be tested using the * class in a method called "checkBatched" currently. This can all be tested using the
* StressTestR4Test method testMultiThreadedUpdateSameResourceInTransaction() * StressTestR4Test method testMultiThreadedUpdateSameResourceInTransaction()
*/ */
if (theException instanceof StaleStateException) { if (theException instanceof org.hibernate.StaleStateException) {
String msg = messageToPrepend + myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "resourceVersionConstraintFailure"); String msg = messageToPrepend + myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "resourceVersionConstraintFailure");
throw new ResourceVersionConflictException(msg); throw new ResourceVersionConflictException(msg);
} }
if (theException instanceof org.hibernate.PessimisticLockException) {
PessimisticLockException ex = (PessimisticLockException) theException;
String sql = defaultString(ex.getSQL()).toUpperCase();
if (sql.contains(ResourceHistoryTable.HFJ_RES_VER)) {
String msg = messageToPrepend + myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "resourceVersionConstraintFailure");
throw new ResourceVersionConflictException(msg);
}
}
return super.convertHibernateAccessException(theException); return super.convertHibernateAccessException(theException);
} }

View File

@ -79,6 +79,7 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
@ -113,8 +114,10 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionSynchronizationAdapter; import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;

View File

@ -33,10 +33,9 @@ import ca.uhn.fhir.jpa.api.model.DeleteConflictList;
import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome; import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome;
import ca.uhn.fhir.jpa.api.model.ExpungeOptions; import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
import ca.uhn.fhir.jpa.api.model.ExpungeOutcome; import ca.uhn.fhir.jpa.api.model.ExpungeOutcome;
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
import ca.uhn.fhir.jpa.delete.DeleteConflictService; import ca.uhn.fhir.jpa.delete.DeleteConflictService;
import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.patch.FhirPatch;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.BaseHasResource; import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
import ca.uhn.fhir.jpa.model.entity.BaseTag; import ca.uhn.fhir.jpa.model.entity.BaseTag;
import ca.uhn.fhir.jpa.model.entity.ForcedId; import ca.uhn.fhir.jpa.model.entity.ForcedId;
@ -46,15 +45,15 @@ import ca.uhn.fhir.jpa.model.entity.TagDefinition;
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum; import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc; import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.patch.FhirPatch;
import ca.uhn.fhir.jpa.patch.JsonPatchUtils;
import ca.uhn.fhir.jpa.patch.XmlPatchUtils;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
import ca.uhn.fhir.jpa.patch.JsonPatchUtils;
import ca.uhn.fhir.jpa.patch.XmlPatchUtils;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.CacheControlDirective; import ca.uhn.fhir.rest.api.CacheControlDirective;
@ -70,6 +69,8 @@ import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.SimplePreResourceAccessDetails; import ca.uhn.fhir.rest.api.server.SimplePreResourceAccessDetails;
import ca.uhn.fhir.rest.api.server.SimplePreResourceShowDetails; import ca.uhn.fhir.rest.api.server.SimplePreResourceShowDetails;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
@ -108,6 +109,7 @@ import org.springframework.transaction.support.TransactionSynchronizationAdapter
import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.persistence.NoResultException; import javax.persistence.NoResultException;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
@ -124,7 +126,6 @@ import java.util.UUID;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@Transactional(propagation = Propagation.REQUIRED)
public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends BaseHapiFhirDao<T> implements IFhirResourceDao<T> { public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends BaseHapiFhirDao<T> implements IFhirResourceDao<T> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirResourceDao.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirResourceDao.class);
@ -151,8 +152,11 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
private IRequestPartitionHelperSvc myRequestPartitionHelperService; private IRequestPartitionHelperSvc myRequestPartitionHelperService;
@Autowired @Autowired
private PartitionSettings myPartitionSettings; private PartitionSettings myPartitionSettings;
@Autowired
private HapiTransactionService myTransactionService;
@Override @Override
@Transactional
public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel, RequestDetails theRequest) { public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel, RequestDetails theRequest) {
StopWatch w = new StopWatch(); StopWatch w = new StopWatch();
BaseHasResource entity = readEntity(theId, theRequest); BaseHasResource entity = readEntity(theId, theRequest);
@ -197,8 +201,20 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return create(theResource, theIfNoneExist, null); return create(theResource, theIfNoneExist, null);
} }
@Override
public DaoMethodOutcome create(final T theResource, String theIfNoneExist, RequestDetails theRequestDetails) {
return create(theResource, theIfNoneExist, true, new TransactionDetails(), theRequestDetails);
}
@Override @Override
public DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing, TransactionDetails theTransactionDetails, RequestDetails theRequestDetails) { public DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing, TransactionDetails theTransactionDetails, RequestDetails theRequestDetails) {
return myTransactionService.execute(theRequestDetails, tx -> doCreateForPost(theResource, theIfNoneExist, thePerformIndexing, theTransactionDetails, theRequestDetails));
}
/**
* Called for FHIR create (POST) operations
*/
private DaoMethodOutcome doCreateForPost(T theResource, String theIfNoneExist, boolean thePerformIndexing, TransactionDetails theTransactionDetails, RequestDetails theRequestDetails) {
if (theResource == null) { if (theResource == null) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "missingBody"); String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "missingBody");
throw new InvalidRequestException(msg); throw new InvalidRequestException(msg);
@ -220,241 +236,14 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequestDetails, theResource, getResourceName()); RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequestDetails, theResource, getResourceName());
return doCreate(theResource, theIfNoneExist, thePerformIndexing, theTransactionDetails, theRequestDetails, requestPartitionId); return doCreateForPostOrPut(theResource, theIfNoneExist, thePerformIndexing, theTransactionDetails, theRequestDetails, requestPartitionId);
}
@Override
public DaoMethodOutcome create(final T theResource, String theIfNoneExist, RequestDetails theRequestDetails) {
return create(theResource, theIfNoneExist, true, new TransactionDetails(), theRequestDetails);
}
private IInstanceValidatorModule getInstanceValidator() {
return myInstanceValidator;
}
@Override
public DaoMethodOutcome delete(IIdType theId) {
return delete(theId, null);
}
@Override
public DaoMethodOutcome delete(IIdType theId, DeleteConflictList theDeleteConflicts, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
validateIdPresentForDelete(theId);
validateDeleteEnabled();
final ResourceTable entity = readEntityLatestVersion(theId, theRequest);
if (theId.hasVersionIdPart() && Long.parseLong(theId.getVersionIdPart()) != entity.getVersion()) {
throw new ResourceVersionConflictException("Trying to delete " + theId + " but this is not the current version");
}
// Don't delete again if it's already deleted
if (entity.getDeleted() != null) {
DaoMethodOutcome outcome = new DaoMethodOutcome();
outcome.setEntity(entity);
IIdType id = getContext().getVersion().newIdType();
id.setValue(entity.getIdDt().getValue());
outcome.setId(id);
IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getContext());
String message = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "successfulDeletes", 1, 0);
String severity = "information";
String code = "informational";
OperationOutcomeUtil.addIssue(getContext(), oo, severity, message, null, code);
outcome.setOperationOutcome(oo);
return outcome;
}
StopWatch w = new StopWatch();
T resourceToDelete = toResource(myResourceType, entity, null, false);
theDeleteConflicts.setResourceIdMarkedForDeletion(theId);
// Notify IServerOperationInterceptors about pre-action call
HookParams hook = new HookParams()
.add(IBaseResource.class, resourceToDelete)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(TransactionDetails.class, theTransactionDetails);
doCallHooks(theRequest, Pointcut.STORAGE_PRESTORAGE_RESOURCE_DELETED, hook);
myDeleteConflictService.validateOkToDelete(theDeleteConflicts, entity, false, theRequest, theTransactionDetails);
preDelete(resourceToDelete, entity);
// Notify interceptors
if (theRequest != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, getContext(), theId.getResourceType(), theId);
notifyInterceptors(RestOperationTypeEnum.DELETE, requestDetails);
}
ResourceTable savedEntity = updateEntityForDelete(theRequest, theTransactionDetails, entity);
resourceToDelete.setId(entity.getIdDt());
// Notify JPA interceptors
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void beforeCommit(boolean readOnly) {
HookParams hookParams = new HookParams()
.add(IBaseResource.class, resourceToDelete)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(TransactionDetails.class, theTransactionDetails);
doCallHooks(theRequest, Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED, hookParams);
}
});
DaoMethodOutcome outcome = toMethodOutcome(theRequest, savedEntity, resourceToDelete).setCreated(true);
IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getContext());
String message = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "successfulDeletes", 1, w.getMillis());
String severity = "information";
String code = "informational";
OperationOutcomeUtil.addIssue(getContext(), oo, severity, message, null, code);
outcome.setOperationOutcome(oo);
return outcome;
}
@Override
public DaoMethodOutcome delete(IIdType theId, RequestDetails theRequestDetails) {
validateIdPresentForDelete(theId);
validateDeleteEnabled();
DeleteConflictList deleteConflicts = new DeleteConflictList();
if (isNotBlank(theId.getValue())) {
deleteConflicts.setResourceIdMarkedForDeletion(theId);
}
StopWatch w = new StopWatch();
DaoMethodOutcome retVal = delete(theId, deleteConflicts, theRequestDetails, new TransactionDetails());
DeleteConflictService.validateDeleteConflictsEmptyOrThrowException(getContext(), deleteConflicts);
ourLog.debug("Processed delete on {} in {}ms", theId.getValue(), w.getMillisAndRestart());
return retVal;
} }
/** /**
* This method gets called by {@link #deleteByUrl(String, DeleteConflictList, RequestDetails)} as well as by * Called both for FHIR create (POST) operations (via {@link #doCreateForPost(IBaseResource, String, boolean, TransactionDetails, RequestDetails)}
* transaction processors * as well as for FHIR update (PUT) where we're doing a create-with-client-assigned-ID (via {@link #doUpdate(IBaseResource, String, boolean, boolean, RequestDetails, TransactionDetails)}.
*/ */
@Override private DaoMethodOutcome doCreateForPostOrPut(T theResource, String theIfNoneExist, boolean thePerformIndexing, TransactionDetails theTransactionDetails, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
public DeleteMethodOutcome deleteByUrl(String theUrl, DeleteConflictList deleteConflicts, RequestDetails theRequest) {
validateDeleteEnabled();
StopWatch w = new StopWatch();
Set<ResourcePersistentId> resourceIds = myMatchResourceUrlService.processMatchUrl(theUrl, myResourceType, theRequest);
if (resourceIds.size() > 1) {
if (myDaoConfig.isAllowMultipleDelete() == false) {
throw new PreconditionFailedException(getContext().getLocalizer().getMessageSanitized(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "DELETE", theUrl, resourceIds.size()));
}
}
TransactionDetails transactionDetails = new TransactionDetails();
List<ResourceTable> deletedResources = new ArrayList<>();
for (ResourcePersistentId pid : resourceIds) {
ResourceTable entity = myEntityManager.find(ResourceTable.class, pid.getId());
deletedResources.add(entity);
T resourceToDelete = toResource(myResourceType, entity, null, false);
// Notify IServerOperationInterceptors about pre-action call
HookParams hooks = new HookParams()
.add(IBaseResource.class, resourceToDelete)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(TransactionDetails.class, transactionDetails);
doCallHooks(theRequest, Pointcut.STORAGE_PRESTORAGE_RESOURCE_DELETED, hooks);
myDeleteConflictService.validateOkToDelete(deleteConflicts, entity, false, theRequest, transactionDetails);
// Notify interceptors
IdDt idToDelete = entity.getIdDt();
if (theRequest != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, idToDelete.getResourceType(), idToDelete);
notifyInterceptors(RestOperationTypeEnum.DELETE, requestDetails);
}
// Perform delete
updateEntityForDelete(theRequest, transactionDetails, entity);
resourceToDelete.setId(entity.getIdDt());
// Notify JPA interceptors
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void beforeCommit(boolean readOnly) {
HookParams hookParams = new HookParams()
.add(IBaseResource.class, resourceToDelete)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(TransactionDetails.class, transactionDetails);
doCallHooks(theRequest, Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED, hookParams);
}
});
}
IBaseOperationOutcome oo;
if (deletedResources.isEmpty()) {
oo = OperationOutcomeUtil.newInstance(getContext());
String message = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "unableToDeleteNotFound", theUrl);
String severity = "warning";
String code = "not-found";
OperationOutcomeUtil.addIssue(getContext(), oo, severity, message, null, code);
} else {
oo = OperationOutcomeUtil.newInstance(getContext());
String message = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "successfulDeletes", deletedResources.size(), w.getMillis());
String severity = "information";
String code = "informational";
OperationOutcomeUtil.addIssue(getContext(), oo, severity, message, null, code);
}
ourLog.debug("Processed delete on {} (matched {} resource(s)) in {}ms", theUrl, deletedResources.size(), w.getMillis());
DeleteMethodOutcome retVal = new DeleteMethodOutcome();
retVal.setDeletedEntities(deletedResources);
retVal.setOperationOutcome(oo);
return retVal;
}
@Override
public DeleteMethodOutcome deleteByUrl(String theUrl, RequestDetails theRequestDetails) {
validateDeleteEnabled();
DeleteConflictList deleteConflicts = new DeleteConflictList();
DeleteMethodOutcome outcome = deleteByUrl(theUrl, deleteConflicts, theRequestDetails);
DeleteConflictService.validateDeleteConflictsEmptyOrThrowException(getContext(), deleteConflicts);
return outcome;
}
private void validateDeleteEnabled() {
if (!myDaoConfig.isDeleteEnabled()) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "deleteBlockedBecauseDisabled");
throw new PreconditionFailedException(msg);
}
}
private void validateIdPresentForDelete(IIdType theId) {
if (theId == null || !theId.hasIdPart()) {
throw new InvalidRequestException("Can not perform delete, no ID provided");
}
}
@PostConstruct
public void detectSearchDaoDisabled() {
if (mySearchDao != null && mySearchDao.isDisabled()) {
mySearchDao = null;
}
}
private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing, TransactionDetails theTransactionDetails, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
StopWatch w = new StopWatch(); StopWatch w = new StopWatch();
preProcessResourceForStorage(theResource); preProcessResourceForStorage(theResource);
@ -579,6 +368,239 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return outcome; return outcome;
} }
private IInstanceValidatorModule getInstanceValidator() {
return myInstanceValidator;
}
@Override
public DaoMethodOutcome delete(IIdType theId) {
return delete(theId, null);
}
@Override
public DaoMethodOutcome delete(IIdType theId, RequestDetails theRequestDetails) {
validateIdPresentForDelete(theId);
validateDeleteEnabled();
return myTransactionService.execute(theRequestDetails, tx -> {
DeleteConflictList deleteConflicts = new DeleteConflictList();
if (isNotBlank(theId.getValue())) {
deleteConflicts.setResourceIdMarkedForDeletion(theId);
}
StopWatch w = new StopWatch();
DaoMethodOutcome retVal = delete(theId, deleteConflicts, theRequestDetails, new TransactionDetails());
DeleteConflictService.validateDeleteConflictsEmptyOrThrowException(getContext(), deleteConflicts);
ourLog.debug("Processed delete on {} in {}ms", theId.getValue(), w.getMillisAndRestart());
return retVal;
});
}
@Override
public DaoMethodOutcome delete(IIdType theId, DeleteConflictList theDeleteConflicts, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails) {
validateIdPresentForDelete(theId);
validateDeleteEnabled();
final ResourceTable entity = readEntityLatestVersion(theId, theRequestDetails);
if (theId.hasVersionIdPart() && Long.parseLong(theId.getVersionIdPart()) != entity.getVersion()) {
throw new ResourceVersionConflictException("Trying to delete " + theId + " but this is not the current version");
}
// Don't delete again if it's already deleted
if (entity.getDeleted() != null) {
DaoMethodOutcome outcome = new DaoMethodOutcome();
outcome.setEntity(entity);
IIdType id = getContext().getVersion().newIdType();
id.setValue(entity.getIdDt().getValue());
outcome.setId(id);
IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getContext());
String message = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "successfulDeletes", 1, 0);
String severity = "information";
String code = "informational";
OperationOutcomeUtil.addIssue(getContext(), oo, severity, message, null, code);
outcome.setOperationOutcome(oo);
return outcome;
}
StopWatch w = new StopWatch();
T resourceToDelete = toResource(myResourceType, entity, null, false);
theDeleteConflicts.setResourceIdMarkedForDeletion(theId);
// Notify IServerOperationInterceptors about pre-action call
HookParams hook = new HookParams()
.add(IBaseResource.class, resourceToDelete)
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
.add(TransactionDetails.class, theTransactionDetails);
doCallHooks(theRequestDetails, Pointcut.STORAGE_PRESTORAGE_RESOURCE_DELETED, hook);
myDeleteConflictService.validateOkToDelete(theDeleteConflicts, entity, false, theRequestDetails, theTransactionDetails);
preDelete(resourceToDelete, entity);
// Notify interceptors
if (theRequestDetails != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, getContext(), theId.getResourceType(), theId);
notifyInterceptors(RestOperationTypeEnum.DELETE, requestDetails);
}
ResourceTable savedEntity = updateEntityForDelete(theRequestDetails, theTransactionDetails, entity);
resourceToDelete.setId(entity.getIdDt());
// Notify JPA interceptors
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void beforeCommit(boolean readOnly) {
HookParams hookParams = new HookParams()
.add(IBaseResource.class, resourceToDelete)
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
.add(TransactionDetails.class, theTransactionDetails);
doCallHooks(theRequestDetails, Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED, hookParams);
}
});
DaoMethodOutcome outcome = toMethodOutcome(theRequestDetails, savedEntity, resourceToDelete).setCreated(true);
IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getContext());
String message = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "successfulDeletes", 1, w.getMillis());
String severity = "information";
String code = "informational";
OperationOutcomeUtil.addIssue(getContext(), oo, severity, message, null, code);
outcome.setOperationOutcome(oo);
return outcome;
}
@Override
public DeleteMethodOutcome deleteByUrl(String theUrl, RequestDetails theRequestDetails) {
validateDeleteEnabled();
return myTransactionService.execute(theRequestDetails, tx -> {
DeleteConflictList deleteConflicts = new DeleteConflictList();
DeleteMethodOutcome outcome = deleteByUrl(theUrl, deleteConflicts, theRequestDetails);
DeleteConflictService.validateDeleteConflictsEmptyOrThrowException(getContext(), deleteConflicts);
return outcome;
});
}
/**
* This method gets called by {@link #deleteByUrl(String, RequestDetails)} as well as by
* transaction processors
*/
@Override
public DeleteMethodOutcome deleteByUrl(String theUrl, DeleteConflictList deleteConflicts, RequestDetails theRequestDetails) {
validateDeleteEnabled();
return myTransactionService.execute(theRequestDetails, tx -> doDeleteByUrl(theUrl, deleteConflicts, theRequestDetails));
}
@Nonnull
private DeleteMethodOutcome doDeleteByUrl(String theUrl, DeleteConflictList deleteConflicts, RequestDetails theRequest) {
StopWatch w = new StopWatch();
Set<ResourcePersistentId> resourceIds = myMatchResourceUrlService.processMatchUrl(theUrl, myResourceType, theRequest);
if (resourceIds.size() > 1) {
if (myDaoConfig.isAllowMultipleDelete() == false) {
throw new PreconditionFailedException(getContext().getLocalizer().getMessageSanitized(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "DELETE", theUrl, resourceIds.size()));
}
}
TransactionDetails transactionDetails = new TransactionDetails();
List<ResourceTable> deletedResources = new ArrayList<>();
for (ResourcePersistentId pid : resourceIds) {
ResourceTable entity = myEntityManager.find(ResourceTable.class, pid.getId());
deletedResources.add(entity);
T resourceToDelete = toResource(myResourceType, entity, null, false);
// Notify IServerOperationInterceptors about pre-action call
HookParams hooks = new HookParams()
.add(IBaseResource.class, resourceToDelete)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(TransactionDetails.class, transactionDetails);
doCallHooks(theRequest, Pointcut.STORAGE_PRESTORAGE_RESOURCE_DELETED, hooks);
myDeleteConflictService.validateOkToDelete(deleteConflicts, entity, false, theRequest, transactionDetails);
// Notify interceptors
IdDt idToDelete = entity.getIdDt();
if (theRequest != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, idToDelete.getResourceType(), idToDelete);
notifyInterceptors(RestOperationTypeEnum.DELETE, requestDetails);
}
// Perform delete
updateEntityForDelete(theRequest, transactionDetails, entity);
resourceToDelete.setId(entity.getIdDt());
// Notify JPA interceptors
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void beforeCommit(boolean readOnly) {
HookParams hookParams = new HookParams()
.add(IBaseResource.class, resourceToDelete)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(TransactionDetails.class, transactionDetails);
doCallHooks(theRequest, Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED, hookParams);
}
});
}
IBaseOperationOutcome oo;
if (deletedResources.isEmpty()) {
oo = OperationOutcomeUtil.newInstance(getContext());
String message = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "unableToDeleteNotFound", theUrl);
String severity = "warning";
String code = "not-found";
OperationOutcomeUtil.addIssue(getContext(), oo, severity, message, null, code);
} else {
oo = OperationOutcomeUtil.newInstance(getContext());
String message = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "successfulDeletes", deletedResources.size(), w.getMillis());
String severity = "information";
String code = "informational";
OperationOutcomeUtil.addIssue(getContext(), oo, severity, message, null, code);
}
ourLog.debug("Processed delete on {} (matched {} resource(s)) in {}ms", theUrl, deletedResources.size(), w.getMillis());
DeleteMethodOutcome retVal = new DeleteMethodOutcome();
retVal.setDeletedEntities(deletedResources);
retVal.setOperationOutcome(oo);
return retVal;
}
private void validateDeleteEnabled() {
if (!myDaoConfig.isDeleteEnabled()) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "deleteBlockedBecauseDisabled");
throw new PreconditionFailedException(msg);
}
}
private void validateIdPresentForDelete(IIdType theId) {
if (theId == null || !theId.hasIdPart()) {
throw new InvalidRequestException("Can not perform delete, no ID provided");
}
}
@PostConstruct
public void detectSearchDaoDisabled() {
if (mySearchDao != null && mySearchDao.isDisabled()) {
mySearchDao = null;
}
}
private <MT extends IBaseMetaType> void doMetaAdd(MT theMetaAdd, BaseHasResource entity) { private <MT extends IBaseMetaType> void doMetaAdd(MT theMetaAdd, BaseHasResource entity) {
List<TagDefinition> tags = toTagList(theMetaAdd); List<TagDefinition> tags = toTagList(theMetaAdd);
@ -647,7 +669,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
@Override @Override
@Transactional(propagation = Propagation.SUPPORTS)
public ExpungeOutcome forceExpungeInExistingTransaction(IIdType theId, ExpungeOptions theExpungeOptions, RequestDetails theRequest) { public ExpungeOutcome forceExpungeInExistingTransaction(IIdType theId, ExpungeOptions theExpungeOptions, RequestDetails theRequest) {
TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager); TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
@ -694,6 +715,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
@Override @Override
@Transactional
public IBundleProvider history(Date theSince, Date theUntil, RequestDetails theRequestDetails) { public IBundleProvider history(Date theSince, Date theUntil, RequestDetails theRequestDetails) {
// Notify interceptors // Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails); ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails);
@ -706,10 +728,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
@Override @Override
@Transactional
public IBundleProvider history(final IIdType theId, final Date theSince, Date theUntil, RequestDetails theRequest) { public IBundleProvider history(final IIdType theId, final Date theSince, Date theUntil, RequestDetails theRequest) {
// Notify interceptors if (theRequest != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, getResourceName(), theId); // Notify interceptors
notifyInterceptors(RestOperationTypeEnum.HISTORY_INSTANCE, requestDetails); ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, getResourceName(), theId);
notifyInterceptors(RestOperationTypeEnum.HISTORY_INSTANCE, requestDetails);
}
StopWatch w = new StopWatch(); StopWatch w = new StopWatch();
@ -755,6 +780,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
@Override @Override
@Transactional
public <MT extends IBaseMetaType> MT metaAddOperation(IIdType theResourceId, MT theMetaAdd, RequestDetails theRequest) { public <MT extends IBaseMetaType> MT metaAddOperation(IIdType theResourceId, MT theMetaAdd, RequestDetails theRequest) {
// Notify interceptors // Notify interceptors
if (theRequest != null) { if (theRequest != null) {
@ -787,6 +813,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
@Override @Override
@Transactional
public <MT extends IBaseMetaType> MT metaDeleteOperation(IIdType theResourceId, MT theMetaDel, RequestDetails theRequest) { public <MT extends IBaseMetaType> MT metaDeleteOperation(IIdType theResourceId, MT theMetaDel, RequestDetails theRequest) {
// Notify interceptors // Notify interceptors
if (theRequest != null) { if (theRequest != null) {
@ -821,6 +848,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
@Override @Override
@Transactional
public <MT extends IBaseMetaType> MT metaGetOperation(Class<MT> theType, IIdType theId, RequestDetails theRequest) { public <MT extends IBaseMetaType> MT metaGetOperation(Class<MT> theType, IIdType theId, RequestDetails theRequest) {
// Notify interceptors // Notify interceptors
if (theRequest != null) { if (theRequest != null) {
@ -842,6 +870,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
@Override @Override
@Transactional
public <MT extends IBaseMetaType> MT metaGetOperation(Class<MT> theType, RequestDetails theRequestDetails) { public <MT extends IBaseMetaType> MT metaGetOperation(Class<MT> theType, RequestDetails theRequestDetails) {
// Notify interceptors // Notify interceptors
if (theRequestDetails != null) { if (theRequestDetails != null) {
@ -859,7 +888,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Override @Override
public DaoMethodOutcome patch(IIdType theId, String theConditionalUrl, PatchTypeEnum thePatchType, String thePatchBody, IBaseParameters theFhirPatchBody, RequestDetails theRequest) { public DaoMethodOutcome patch(IIdType theId, String theConditionalUrl, PatchTypeEnum thePatchType, String thePatchBody, IBaseParameters theFhirPatchBody, RequestDetails theRequest) {
return myTransactionService.execute(theRequest, tx -> doPatch(theId, theConditionalUrl, thePatchType, thePatchBody, theFhirPatchBody, theRequest));
}
private DaoMethodOutcome doPatch(IIdType theId, String theConditionalUrl, PatchTypeEnum thePatchType, String thePatchBody, IBaseParameters theFhirPatchBody, RequestDetails theRequest) {
ResourceTable entityToUpdate; ResourceTable entityToUpdate;
if (isNotBlank(theConditionalUrl)) { if (isNotBlank(theConditionalUrl)) {
@ -890,10 +922,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
IBaseResource destination; IBaseResource destination;
switch (thePatchType) { switch (thePatchType) {
case JSON_PATCH: case JSON_PATCH:
destination = JsonPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody); destination = JsonPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody);
break; break;
case XML_PATCH: case XML_PATCH:
destination = XmlPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody); destination = XmlPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody);
break; break;
case FHIR_PATCH_XML: case FHIR_PATCH_XML:
case FHIR_PATCH_JSON: case FHIR_PATCH_JSON:
@ -932,6 +964,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
@Override @Override
@Transactional
public T readByPid(ResourcePersistentId thePid) { public T readByPid(ResourcePersistentId thePid) {
StopWatch w = new StopWatch(); StopWatch w = new StopWatch();
@ -950,16 +983,19 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
@Override @Override
@Transactional
public T read(IIdType theId) { public T read(IIdType theId) {
return read(theId, null); return read(theId, null);
} }
@Override @Override
@Transactional
public T read(IIdType theId, RequestDetails theRequestDetails) { public T read(IIdType theId, RequestDetails theRequestDetails) {
return read(theId, theRequestDetails, false); return read(theId, theRequestDetails, false);
} }
@Override @Override
@Transactional
public T read(IIdType theId, RequestDetails theRequest, boolean theDeletedOk) { public T read(IIdType theId, RequestDetails theRequest, boolean theDeletedOk) {
validateResourceTypeAndThrowInvalidRequestException(theId); validateResourceTypeAndThrowInvalidRequestException(theId);
@ -1012,11 +1048,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
@Override @Override
@Transactional
public BaseHasResource readEntity(IIdType theId, RequestDetails theRequest) { public BaseHasResource readEntity(IIdType theId, RequestDetails theRequest) {
return readEntity(theId, true, theRequest); return readEntity(theId, true, theRequest);
} }
@Override @Override
@Transactional
public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId, RequestDetails theRequest) { public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId, RequestDetails theRequest) {
validateResourceTypeAndThrowInvalidRequestException(theId); validateResourceTypeAndThrowInvalidRequestException(theId);
@ -1099,6 +1137,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Override @Override
public void reindex(T theResource, ResourceTable theEntity) { public void reindex(T theResource, ResourceTable theEntity) {
assert TransactionSynchronizationManager.isActualTransactionActive();
ourLog.debug("Indexing resource {} - PID {}", theEntity.getIdDt().getValue(), theEntity.getId()); ourLog.debug("Indexing resource {} - PID {}", theEntity.getIdDt().getValue(), theEntity.getId());
if (theResource != null) { if (theResource != null) {
CURRENTLY_REINDEXING.put(theResource, Boolean.TRUE); CURRENTLY_REINDEXING.put(theResource, Boolean.TRUE);
@ -1111,11 +1151,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
} }
@Transactional
@Override @Override
public void removeTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm) { public void removeTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm) {
removeTag(theId, theTagType, theScheme, theTerm, null); removeTag(theId, theTagType, theScheme, theTerm, null);
} }
@Transactional
@Override @Override
public void removeTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, RequestDetails theRequest) { public void removeTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, RequestDetails theRequest) {
// Notify interceptors // Notify interceptors
@ -1216,26 +1258,28 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Override @Override
public Set<ResourcePersistentId> searchForIds(SearchParameterMap theParams, RequestDetails theRequest) { public Set<ResourcePersistentId> searchForIds(SearchParameterMap theParams, RequestDetails theRequest) {
theParams.setLoadSynchronousUpTo(10000); return myTransactionService.execute(theRequest, tx -> {
theParams.setLoadSynchronousUpTo(10000);
ISearchBuilder builder = mySearchBuilderFactory.newSearchBuilder(this, getResourceName(), getResourceType()); ISearchBuilder builder = mySearchBuilderFactory.newSearchBuilder(this, getResourceName(), getResourceType());
HashSet<ResourcePersistentId> retVal = new HashSet<>(); HashSet<ResourcePersistentId> retVal = new HashSet<>();
String uuid = UUID.randomUUID().toString(); String uuid = UUID.randomUUID().toString();
SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequest, uuid); SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequest, uuid);
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, getResourceName()); RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, getResourceName());
try (IResultIterator iter = builder.createQuery(theParams, searchRuntimeDetails, theRequest, requestPartitionId)) { try (IResultIterator iter = builder.createQuery(theParams, searchRuntimeDetails, theRequest, requestPartitionId)) {
while (iter.hasNext()) { while (iter.hasNext()) {
retVal.add(iter.next()); retVal.add(iter.next());
}
} catch (IOException e) {
ourLog.error("IO failure during database access", e);
} }
} catch (IOException e) {
ourLog.error("IO failure during database access", e);
}
return retVal; return retVal;
});
} }
protected <MT extends IBaseMetaType> MT toMetaDt(Class<MT> theType, Collection<TagDefinition> tagDefinitions) { protected <MT extends IBaseMetaType> MT toMetaDt(Class<MT> theType, Collection<TagDefinition> tagDefinitions) {
@ -1303,10 +1347,21 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "missingBody"); String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "missingBody");
throw new InvalidRequestException(msg); throw new InvalidRequestException(msg);
} }
assert theResource.getIdElement().hasIdPart() || isNotBlank(theMatchUrl);
return myTransactionService.execute(theRequest, tx -> doUpdate(theResource, theMatchUrl, thePerformIndexing, theForceUpdateVersion, theRequest, theTransactionDetails));
}
private DaoMethodOutcome doUpdate(T theResource, String theMatchUrl, boolean thePerformIndexing, boolean theForceUpdateVersion, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
StopWatch w = new StopWatch(); StopWatch w = new StopWatch();
preProcessResourceForStorage(theResource); T resource = theResource;
if (JpaInterceptorBroadcaster.hasHooks(Pointcut.STORAGE_VERSION_CONFLICT, myInterceptorBroadcaster, theRequest)) {
resource = (T) getContext().getResourceDefinition(theResource).newInstance();
getContext().newTerser().cloneInto(theResource, resource, false);
}
preProcessResourceForStorage(resource);
final ResourceTable entity; final ResourceTable entity;
@ -1321,7 +1376,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
entity = myEntityManager.find(ResourceTable.class, pid.getId()); entity = myEntityManager.find(ResourceTable.class, pid.getId());
resourceId = entity.getIdDt(); resourceId = entity.getIdDt();
} else { } else {
return create(theResource, null, thePerformIndexing, theTransactionDetails, theRequest); return create(resource, null, thePerformIndexing, theTransactionDetails, theRequest);
} }
} else { } else {
/* /*
@ -1330,13 +1385,15 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
* BaseOutcomeReturningMethodBindingWithResourceParam * BaseOutcomeReturningMethodBindingWithResourceParam
*/ */
resourceId = theResource.getIdElement(); resourceId = theResource.getIdElement();
assert resourceId != null;
assert resourceId.hasIdPart();
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, getResourceName()); RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, getResourceName());
try { try {
entity = readEntityLatestVersion(resourceId, requestPartitionId); entity = readEntityLatestVersion(resourceId, requestPartitionId);
} catch (ResourceNotFoundException e) { } catch (ResourceNotFoundException e) {
requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequest, theResource, getResourceName()); requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequest, theResource, getResourceName());
return doCreate(theResource, null, thePerformIndexing, theTransactionDetails, theRequest, requestPartitionId); return doCreateForPostOrPut(resource, null, thePerformIndexing, theTransactionDetails, theRequest, requestPartitionId);
} }
} }
@ -1372,8 +1429,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
* directly. So we just bail now. * directly. So we just bail now.
*/ */
if (!thePerformIndexing) { if (!thePerformIndexing) {
theResource.setId(entity.getIdDt().getValue()); resource.setId(entity.getIdDt().getValue());
DaoMethodOutcome outcome = toMethodOutcome(theRequest, entity, theResource).setCreated(wasDeleted); DaoMethodOutcome outcome = toMethodOutcome(theRequest, entity, resource).setCreated(wasDeleted);
outcome.setPreviousResource(oldResource); outcome.setPreviousResource(oldResource);
return outcome; return outcome;
} }
@ -1381,11 +1438,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
/* /*
* Otherwise, we're not in a transaction * Otherwise, we're not in a transaction
*/ */
ResourceTable savedEntity = updateInternal(theRequest, theResource, thePerformIndexing, theForceUpdateVersion, entity, resourceId, oldResource, theTransactionDetails); ResourceTable savedEntity = updateInternal(theRequest, resource, thePerformIndexing, theForceUpdateVersion, entity, resourceId, oldResource, theTransactionDetails);
DaoMethodOutcome outcome = toMethodOutcome(theRequest, savedEntity, theResource).setCreated(wasDeleted); DaoMethodOutcome outcome = toMethodOutcome(theRequest, savedEntity, resource).setCreated(wasDeleted);
if (!thePerformIndexing) { if (!thePerformIndexing) {
outcome.setId(theResource.getIdElement()); IIdType id = getContext().getVersion().newIdType();
id.setValue(entity.getIdDt().getValue());
outcome.setId(id);
} }
String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "successfulUpdate", outcome.getId(), w.getMillisAndRestart()); String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "successfulUpdate", outcome.getId(), w.getMillisAndRestart());

View File

@ -32,6 +32,7 @@ import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.api.model.DeleteConflict; import ca.uhn.fhir.jpa.api.model.DeleteConflict;
import ca.uhn.fhir.jpa.api.model.DeleteConflictList; import ca.uhn.fhir.jpa.api.model.DeleteConflictList;
import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome; import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome;
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
import ca.uhn.fhir.jpa.delete.DeleteConflictService; import ca.uhn.fhir.jpa.delete.DeleteConflictService;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
@ -64,7 +65,6 @@ import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.ResourceReferenceInfo; import ca.uhn.fhir.util.ResourceReferenceInfo;
import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets;
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ArrayListMultimap;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Bundle;
@ -83,6 +83,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -121,6 +122,8 @@ public abstract class BaseTransactionProcessor {
private IInterceptorBroadcaster myInterceptorBroadcaster; private IInterceptorBroadcaster myInterceptorBroadcaster;
@Autowired @Autowired
private MatchResourceUrlService myMatchResourceUrlService; private MatchResourceUrlService myMatchResourceUrlService;
@Autowired
private HapiTransactionService myHapiTransactionService;
@PostConstruct @PostConstruct
public void start() { public void start() {
@ -344,9 +347,6 @@ public abstract class BaseTransactionProcessor {
final TransactionDetails transactionDetails = new TransactionDetails(); final TransactionDetails transactionDetails = new TransactionDetails();
final StopWatch transactionStopWatch = new StopWatch(); final StopWatch transactionStopWatch = new StopWatch();
final Set<IIdType> allIds = new LinkedHashSet<>();
final Map<IIdType, IIdType> idSubstitutions = new HashMap<>();
final Map<IIdType, DaoMethodOutcome> idToPersistedOutcome = new HashMap<>();
List<IBase> requestEntries = myVersionAdapter.getEntries(theRequest); List<IBase> requestEntries = myVersionAdapter.getEntries(theRequest);
// Do all entries have a verb? // Do all entries have a verb?
@ -403,13 +403,16 @@ public abstract class BaseTransactionProcessor {
* heavy load with lots of concurrent transactions using all available * heavy load with lots of concurrent transactions using all available
* database connections. * database connections.
*/ */
TransactionTemplate txManager = new TransactionTemplate(myTxManager); TransactionCallback<Map<IBase, IBasePersistedResource>> txCallback = status -> {
Map<IBase, IBasePersistedResource> entriesToProcess = txManager.execute(status -> { final Set<IIdType> allIds = new LinkedHashSet<>();
final Map<IIdType, IIdType> idSubstitutions = new HashMap<>();
final Map<IIdType, DaoMethodOutcome> idToPersistedOutcome = new HashMap<>();
Map<IBase, IBasePersistedResource> retVal = doTransactionWriteOperations(theRequestDetails, theActionName, transactionDetails, allIds, idSubstitutions, idToPersistedOutcome, response, originalRequestOrder, entries, transactionStopWatch); Map<IBase, IBasePersistedResource> retVal = doTransactionWriteOperations(theRequestDetails, theActionName, transactionDetails, allIds, idSubstitutions, idToPersistedOutcome, response, originalRequestOrder, entries, transactionStopWatch);
transactionStopWatch.startTask("Commit writes to database"); transactionStopWatch.startTask("Commit writes to database");
return retVal; return retVal;
}); };
Map<IBase, IBasePersistedResource> entriesToProcess = myHapiTransactionService.execute(theRequestDetails, txCallback);
transactionStopWatch.endCurrentTask(); transactionStopWatch.endCurrentTask();
for (Map.Entry<IBase, IBasePersistedResource> nextEntry : entriesToProcess.entrySet()) { for (Map.Entry<IBase, IBasePersistedResource> nextEntry : entriesToProcess.entrySet()) {

View File

@ -57,6 +57,8 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import static ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoValueSetDstu3.vsValidateCodeOptions;
import static ca.uhn.fhir.jpa.util.LogicUtil.multiXor;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -291,102 +293,14 @@ public class FhirResourceDaoValueSetDstu2 extends BaseHapiFhirResourceDao<ValueS
// nothing // nothing
} }
private String toStringOrNull(IPrimitiveType<String> thePrimitive) { public static String toStringOrNull(IPrimitiveType<String> thePrimitive) {
return thePrimitive != null ? thePrimitive.getValue() : null; return thePrimitive != null ? thePrimitive.getValue() : null;
} }
@Override @Override
public ValidateCodeResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode, public IValidationSupport.CodeValidationResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode,
IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, CodingDt theCoding, CodeableConceptDt theCodeableConcept, RequestDetails theRequest) { IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, CodingDt theCoding, CodeableConceptDt theCodeableConcept, RequestDetails theRequest) {
List<IIdType> valueSetIds; return myTerminologySvc.validateCode(vsValidateCodeOptions(), theId, toStringOrNull(theValueSetIdentifier), toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
boolean haveCodeableConcept = theCodeableConcept != null && theCodeableConcept.getCoding().size() > 0;
boolean haveCoding = theCoding != null && theCoding.isEmpty() == false;
boolean haveCode = theCode != null && theCode.isEmpty() == false;
if (!haveCodeableConcept && !haveCoding && !haveCode) {
throw new InvalidRequestException("No code, coding, or codeableConcept provided to validate");
}
if (!multiXor(haveCodeableConcept, haveCoding, haveCode)) {
throw new InvalidRequestException("$validate-code can only validate (system AND code) OR (coding) OR (codeableConcept)");
}
boolean haveIdentifierParam = theValueSetIdentifier != null && theValueSetIdentifier.isEmpty() == false;
if (theId != null) {
valueSetIds = Collections.singletonList(theId);
} else if (haveIdentifierParam) {
Set<ResourcePersistentId> ids = searchForIds(new SearchParameterMap(ValueSet.SP_IDENTIFIER, new TokenParam(null, theValueSetIdentifier.getValue())), theRequest);
valueSetIds = new ArrayList<>();
for (ResourcePersistentId next : ids) {
IIdType id = myIdHelperService.translatePidIdToForcedId(myFhirContext, "ValueSet", next);
valueSetIds.add(id);
}
} else {
if (theCode == null || theCode.isEmpty()) {
throw new InvalidRequestException("Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate.");
}
String code = theCode.getValue();
String system = toStringOrNull(theSystem);
valueSetIds = findCodeSystemIdsContainingSystemAndCode(code, system, theRequest);
}
for (IIdType nextId : valueSetIds) {
ValueSet expansion = expand(nextId, null, theRequest);
List<ExpansionContains> contains = expansion.getExpansion().getContains();
ValidateCodeResult result = validateCodeIsInContains(contains, toStringOrNull(theSystem), toStringOrNull(theCode), theCoding, theCodeableConcept);
if (result != null) {
if (theDisplay != null && isNotBlank(theDisplay.getValue()) && isNotBlank(result.getDisplay())) {
if (!theDisplay.getValue().equals(result.getDisplay())) {
return new ValidateCodeResult(false, "Display for code does not match", result.getDisplay());
}
}
return result;
}
}
return new ValidateCodeResult(false, "Code not found", null);
}
private ValidateCodeResult validateCodeIsInContains(List<ExpansionContains> contains, String theSystem, String theCode, CodingDt theCoding,
CodeableConceptDt theCodeableConcept) {
for (ExpansionContains nextCode : contains) {
ValidateCodeResult result = validateCodeIsInContains(nextCode.getContains(), theSystem, theCode, theCoding, theCodeableConcept);
if (result != null) {
return result;
}
String system = nextCode.getSystem();
String code = nextCode.getCode();
if (isNotBlank(theCode)) {
if (theCode.equals(code) && (isBlank(theSystem) || theSystem.equals(system))) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
} else if (theCoding != null) {
if (StringUtils.equals(system, theCoding.getSystem()) && StringUtils.equals(code, theCoding.getCode())) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
} else {
for (CodingDt next : theCodeableConcept.getCoding()) {
if (StringUtils.equals(system, next.getSystem()) && StringUtils.equals(code, next.getCode())) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
}
}
}
return null;
}
private static boolean multiXor(boolean... theValues) {
int count = 0;
for (int i = 0; i < theValues.length; i++) {
if (theValues[i]) {
count++;
}
}
return count == 1;
} }
} }

View File

@ -26,6 +26,7 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.api.model.DeleteConflictList; import ca.uhn.fhir.jpa.api.model.DeleteConflictList;
import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome; import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome;
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
import ca.uhn.fhir.jpa.delete.DeleteConflictService; import ca.uhn.fhir.jpa.delete.DeleteConflictService;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
@ -73,7 +74,6 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -109,14 +109,13 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
private DaoRegistry myDaoRegistry; private DaoRegistry myDaoRegistry;
@Autowired @Autowired
private MatchResourceUrlService myMatchResourceUrlService; private MatchResourceUrlService myMatchResourceUrlService;
@Autowired
private HapiTransactionService myHapiTransactionalService;
private Bundle batch(final RequestDetails theRequestDetails, Bundle theRequest) { private Bundle batch(final RequestDetails theRequestDetails, Bundle theRequest) {
ourLog.info("Beginning batch with {} resources", theRequest.getEntry().size()); ourLog.info("Beginning batch with {} resources", theRequest.getEntry().size());
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
Bundle resp = new Bundle(); Bundle resp = new Bundle();
resp.setType(BundleTypeEnum.BATCH_RESPONSE); resp.setType(BundleTypeEnum.BATCH_RESPONSE);
@ -144,7 +143,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
// create their own // create their own
nextResponseBundle = callback.doInTransaction(null); nextResponseBundle = callback.doInTransaction(null);
} else { } else {
nextResponseBundle = txTemplate.execute(callback); nextResponseBundle = myHapiTransactionalService.execute(theRequestDetails, callback);
} }
caughtEx = null; caughtEx = null;

View File

@ -90,6 +90,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
@ -245,6 +246,7 @@ public class SearchBuilder implements ISearchBuilder {
@Override @Override
public Iterator<Long> createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest, @Nonnull RequestPartitionId theRequestPartitionId) { public Iterator<Long> createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest, @Nonnull RequestPartitionId theRequestPartitionId) {
assert theRequestPartitionId != null; assert theRequestPartitionId != null;
assert TransactionSynchronizationManager.isActualTransactionActive();
init(theParams, theSearchUuid, theRequestPartitionId); init(theParams, theSearchUuid, theRequestPartitionId);
@ -263,6 +265,7 @@ public class SearchBuilder implements ISearchBuilder {
@Override @Override
public IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntimeDetails, RequestDetails theRequest, @Nonnull RequestPartitionId theRequestPartitionId) { public IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntimeDetails, RequestDetails theRequest, @Nonnull RequestPartitionId theRequestPartitionId) {
assert theRequestPartitionId != null; assert theRequestPartitionId != null;
assert TransactionSynchronizationManager.isActualTransactionActive();
init(theParams, theSearchRuntimeDetails.getSearchUuid(), theRequestPartitionId); init(theParams, theSearchRuntimeDetails.getSearchUuid(), theRequestPartitionId);

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.dstu3;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
@ -29,14 +30,12 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.util.LogicUtil;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.ElementUtil; import ca.uhn.fhir.util.ElementUtil;
import org.apache.commons.codec.binary.StringUtils;
import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding; import org.hl7.fhir.dstu3.model.Coding;
@ -50,15 +49,14 @@ import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import static ca.uhn.fhir.jpa.dao.FhirResourceDaoValueSetDstu2.toStringOrNull;
import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoValueSetR4.validateHaveExpansionOrThrowInternalErrorException; import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoValueSetR4.validateHaveExpansionOrThrowInternalErrorException;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -81,7 +79,7 @@ public class FhirResourceDaoValueSetDstu3 extends BaseHapiFhirResourceDao<ValueS
@Override @Override
public void start() { public void start() {
super.start(); super.start();
myValidationSupport = getApplicationContext().getBean(IValidationSupport.class,"myJpaValidationSupportChain" ); myValidationSupport = getApplicationContext().getBean(IValidationSupport.class, "myJpaValidationSupportChain");
} }
@Override @Override
@ -264,108 +262,10 @@ public class FhirResourceDaoValueSetDstu3 extends BaseHapiFhirResourceDao<ValueS
} }
@Override @Override
public IFhirResourceDaoValueSet.ValidateCodeResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode, public IValidationSupport.CodeValidationResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode,
IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding, IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding,
CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) { CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) {
return myTerminologySvc.validateCode(vsValidateCodeOptions(), theId, toStringOrNull(theValueSetIdentifier), toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
List<IIdType> valueSetIds = Collections.emptyList();
boolean haveCodeableConcept = theCodeableConcept != null && theCodeableConcept.getCoding().size() > 0;
boolean haveCoding = theCoding != null && theCoding.isEmpty() == false;
boolean haveCode = theCode != null && theCode.isEmpty() == false;
if (!haveCodeableConcept && !haveCoding && !haveCode) {
throw new InvalidRequestException("No code, coding, or codeableConcept provided to validate");
}
if (!LogicUtil.multiXor(haveCodeableConcept, haveCoding, haveCode)) {
throw new InvalidRequestException("$validate-code can only validate (system AND code) OR (coding) OR (codeableConcept)");
}
boolean haveIdentifierParam = theValueSetIdentifier != null && theValueSetIdentifier.isEmpty() == false;
ValueSet vs = null;
boolean isBuiltInValueSet = false;
if (theId != null) {
vs = read(theId, theRequestDetails);
} else if (haveIdentifierParam) {
vs = (ValueSet) myDefaultProfileValidationSupport.fetchValueSet(theValueSetIdentifier.getValue());
if (vs == null) {
vs = (ValueSet) myValidationSupport.fetchValueSet(theValueSetIdentifier.getValue());
if (vs == null) {
throw new InvalidRequestException("Unknown ValueSet identifier: " + theValueSetIdentifier.getValue());
}
} else {
isBuiltInValueSet = true;
}
} else {
if (theCode == null || theCode.isEmpty()) {
throw new InvalidRequestException("Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate.");
}
// String code = theCode.getValue();
// String system = toStringOrNull(theSystem);
IValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(theCode, theSystem, null, null);
if (result != null && result.isFound()) {
IFhirResourceDaoValueSet.ValidateCodeResult retVal = new ValidateCodeResult(true, "Found code", result.getCodeDisplay());
return retVal;
}
}
if (vs != null) {
ValidateCodeResult result;
if (myDaoConfig.isPreExpandValueSets() && !isBuiltInValueSet && myTerminologySvc.isValueSetPreExpandedForCodeValidation(vs)) {
result = myTerminologySvc.validateCodeIsInPreExpandedValueSet(new ValidationOptions(), vs, toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
} else {
ValueSet expansion = doExpand(vs);
List<ValueSetExpansionContainsComponent> contains = expansion.getExpansion().getContains();
result = validateCodeIsInContains(contains, toStringOrNull(theSystem), toStringOrNull(theCode), theCoding, theCodeableConcept);
}
if (result != null) {
if (theDisplay != null && isNotBlank(theDisplay.getValue()) && isNotBlank(result.getDisplay())) {
if (!theDisplay.getValue().equals(result.getDisplay())) {
return new ValidateCodeResult(false, "Display for code does not match", result.getDisplay());
}
}
return result;
}
}
return new ValidateCodeResult(false, "Code not found", null);
}
private String toStringOrNull(IPrimitiveType<String> thePrimitive) {
return thePrimitive != null ? thePrimitive.getValue() : null;
}
private IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInContains(List<ValueSetExpansionContainsComponent> contains, String theSystem, String theCode,
Coding theCoding, CodeableConcept theCodeableConcept) {
for (ValueSetExpansionContainsComponent nextCode : contains) {
IFhirResourceDaoValueSet.ValidateCodeResult result = validateCodeIsInContains(nextCode.getContains(), theSystem, theCode, theCoding, theCodeableConcept);
if (result != null) {
return result;
}
String system = nextCode.getSystem();
String code = nextCode.getCode();
if (isNotBlank(theCode)) {
if (theCode.equals(code) && (isBlank(theSystem) || theSystem.equals(system))) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
} else if (theCoding != null) {
if (StringUtils.equals(system, theCoding.getSystem()) && StringUtils.equals(code, theCoding.getCode())) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
} else {
for (Coding next : theCodeableConcept.getCoding()) {
if (StringUtils.equals(system, next.getSystem()) && StringUtils.equals(code, next.getCode())) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
}
}
}
return null;
} }
@Override @Override
@ -395,4 +295,8 @@ public class FhirResourceDaoValueSetDstu3 extends BaseHapiFhirResourceDao<ValueS
return retVal; return retVal;
} }
public static ConceptValidationOptions vsValidateCodeOptions() {
return new ConceptValidationOptions().setValidateDisplay(true);
}
} }

View File

@ -133,6 +133,8 @@ public class IdHelperService {
*/ */
@Nonnull @Nonnull
public ResourcePersistentId resolveResourcePersistentIds(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, String theId) { public ResourcePersistentId resolveResourcePersistentIds(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, String theId) {
Validate.notNull(theId, "theId must not be null");
Long retVal; Long retVal;
if (myDaoConfig.getResourceClientIdStrategy() == DaoConfig.ClientIdStrategyEnum.ANY || !isValidPid(theId)) { if (myDaoConfig.getResourceClientIdStrategy() == DaoConfig.ClientIdStrategyEnum.ANY || !isValidPid(theId)) {
if (myDaoConfig.isDeleteEnabled()) { if (myDaoConfig.isDeleteEnabled()) {

View File

@ -497,11 +497,6 @@ class PredicateBuilderReference extends BasePredicateBuilder {
} }
} }
// one value
if (path.size() == 1) {
return myCriteriaBuilder.equal(from.get("mySourcePath").as(String.class), path.get(0));
}
// multiple values // multiple values
return from.get("mySourcePath").in(path); return from.get("mySourcePath").in(path);
} }

View File

@ -366,12 +366,12 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
break; break;
} }
Predicate predicate; /*
if (values.size() == 1) { * Note: At one point we had an IF-ELSE here that did an equals if there was only 1 value, and an IN if there
predicate = myCriteriaBuilder.equal(hashField, values.get(0)); * was more than 1. This caused a performance regression for some reason in Postgres though. So maybe simpler
} else { * is better..
predicate = hashField.in(values); */
} Predicate predicate = hashField.in(values);
if (theModifier == TokenParamModifier.NOT) { if (theModifier == TokenParamModifier.NOT) {
Predicate identityPredicate = theBuilder.equal(theFrom.get("myHashIdentity").as(Long.class), BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName)); Predicate identityPredicate = theBuilder.equal(theFrom.get("myHashIdentity").as(Long.class), BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName));

View File

@ -31,7 +31,6 @@ import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.util.LogicUtil;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -49,13 +48,13 @@ import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r4.model.ValueSet.ConceptSetFilterComponent; import org.hl7.fhir.r4.model.ValueSet.ConceptSetFilterComponent;
import org.hl7.fhir.r4.model.ValueSet.FilterOperator; import org.hl7.fhir.r4.model.ValueSet.FilterOperator;
import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import static ca.uhn.fhir.jpa.dao.FhirResourceDaoValueSetDstu2.toStringOrNull;
import static ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoValueSetDstu3.vsValidateCodeOptions;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -243,108 +242,11 @@ public class FhirResourceDaoValueSetR4 extends BaseHapiFhirResourceDao<ValueSet>
} }
@Override @Override
public ValidateCodeResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode, public IValidationSupport.CodeValidationResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode,
IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding, IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding,
CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) { CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) {
List<IIdType> valueSetIds = Collections.emptyList(); return myTerminologySvc.validateCode(vsValidateCodeOptions(), theId, toStringOrNull(theValueSetIdentifier), toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
boolean haveCodeableConcept = theCodeableConcept != null && theCodeableConcept.getCoding().size() > 0;
boolean haveCoding = theCoding != null && !theCoding.isEmpty();
boolean haveCode = theCode != null && !theCode.isEmpty();
if (!haveCodeableConcept && !haveCoding && !haveCode) {
throw new InvalidRequestException("No code, coding, or codeableConcept provided to validate");
}
if (!LogicUtil.multiXor(haveCodeableConcept, haveCoding, haveCode)) {
throw new InvalidRequestException("$validate-code can only validate (system AND code) OR (coding) OR (codeableConcept)");
}
boolean haveIdentifierParam = theValueSetIdentifier != null && !theValueSetIdentifier.isEmpty();
ValueSet vs = null;
boolean isBuiltInValueSet = false;
if (theId != null) {
vs = read(theId, theRequestDetails);
} else if (haveIdentifierParam) {
vs = (ValueSet) myDefaultProfileValidationSupport.fetchValueSet(theValueSetIdentifier.getValue());
if (vs == null) {
vs = (ValueSet) myValidationSupport.fetchValueSet(theValueSetIdentifier.getValue());
if (vs == null) {
throw new InvalidRequestException("Unknown ValueSet identifier: " + theValueSetIdentifier.getValue());
}
} else {
isBuiltInValueSet = true;
}
} else {
if (theCode == null || theCode.isEmpty()) {
throw new InvalidRequestException("Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate.");
}
// String code = theCode.getValue();
// String system = toStringOrNull(theSystem);
IValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(theCode, theSystem, null, null);
if (result.isFound()) {
ValidateCodeResult retVal = new ValidateCodeResult(true, "Found code", result.getCodeDisplay());
return retVal;
}
}
if (vs != null) {
ValidateCodeResult result;
if (myDaoConfig.isPreExpandValueSets() && !isBuiltInValueSet && myTerminologySvc.isValueSetPreExpandedForCodeValidation(vs)) {
result = myTerminologySvc.validateCodeIsInPreExpandedValueSet(new ValidationOptions(), vs, toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
} else {
ValueSet expansion = doExpand(vs);
List<ValueSetExpansionContainsComponent> contains = expansion.getExpansion().getContains();
result = validateCodeIsInContains(contains, toStringOrNull(theSystem), toStringOrNull(theCode), theCoding, theCodeableConcept);
}
if (result != null) {
if (theDisplay != null && isNotBlank(theDisplay.getValue()) && isNotBlank(result.getDisplay())) {
if (!theDisplay.getValue().equals(result.getDisplay())) {
return new ValidateCodeResult(false, "Display for code does not match", result.getDisplay());
}
}
return result;
}
}
return new ValidateCodeResult(false, "Code not found", null);
}
private String toStringOrNull(IPrimitiveType<String> thePrimitive) {
return thePrimitive != null ? thePrimitive.getValue() : null;
}
private ValidateCodeResult validateCodeIsInContains(List<ValueSetExpansionContainsComponent> contains, String theSystem, String theCode,
Coding theCoding, CodeableConcept theCodeableConcept) {
for (ValueSetExpansionContainsComponent nextCode : contains) {
ValidateCodeResult result = validateCodeIsInContains(nextCode.getContains(), theSystem, theCode, theCoding, theCodeableConcept);
if (result != null) {
return result;
}
String system = nextCode.getSystem();
String code = nextCode.getCode();
if (isNotBlank(theCode)) {
if (theCode.equals(code) && (isBlank(theSystem) || theSystem.equals(system))) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
} else if (theCoding != null) {
if (StringUtils.equals(system, theCoding.getSystem()) && StringUtils.equals(code, theCoding.getCode())) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
} else {
for (Coding next : theCodeableConcept.getCoding()) {
if (StringUtils.equals(system, next.getSystem()) && StringUtils.equals(code, next.getCode())) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
}
}
}
return null;
} }
@Override @Override

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.r5;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
@ -55,6 +56,8 @@ import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import static ca.uhn.fhir.jpa.dao.FhirResourceDaoValueSetDstu2.toStringOrNull;
import static ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoValueSetDstu3.vsValidateCodeOptions;
import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoValueSetR4.validateHaveExpansionOrThrowInternalErrorException; import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoValueSetR4.validateHaveExpansionOrThrowInternalErrorException;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -245,108 +248,10 @@ public class FhirResourceDaoValueSetR5 extends BaseHapiFhirResourceDao<ValueSet>
} }
@Override @Override
public ValidateCodeResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode, public IValidationSupport.CodeValidationResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode,
IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding, IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding,
CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) { CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) {
return myTerminologySvc.validateCode(vsValidateCodeOptions(), theId, toStringOrNull(theValueSetIdentifier), toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
List<IIdType> valueSetIds = Collections.emptyList();
boolean haveCodeableConcept = theCodeableConcept != null && theCodeableConcept.getCoding().size() > 0;
boolean haveCoding = theCoding != null && theCoding.isEmpty() == false;
boolean haveCode = theCode != null && theCode.isEmpty() == false;
if (!haveCodeableConcept && !haveCoding && !haveCode) {
throw new InvalidRequestException("No code, coding, or codeableConcept provided to validate");
}
if (!LogicUtil.multiXor(haveCodeableConcept, haveCoding, haveCode)) {
throw new InvalidRequestException("$validate-code can only validate (system AND code) OR (coding) OR (codeableConcept)");
}
boolean haveIdentifierParam = theValueSetIdentifier != null && theValueSetIdentifier.isEmpty() == false;
ValueSet vs = null;
boolean isBuiltInValueSet = false;
if (theId != null) {
vs = read(theId, theRequestDetails);
} else if (haveIdentifierParam) {
vs = (ValueSet) myDefaultProfileValidationSupport.fetchValueSet(theValueSetIdentifier.getValue());
if (vs == null) {
vs = (ValueSet) myValidationSupport.fetchValueSet(theValueSetIdentifier.getValue());
if (vs == null) {
throw new InvalidRequestException("Unknown ValueSet identifier: " + theValueSetIdentifier.getValue());
}
} else {
isBuiltInValueSet = true;
}
} else {
if (theCode == null || theCode.isEmpty()) {
throw new InvalidRequestException("Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate.");
}
// String code = theCode.getValue();
// String system = toStringOrNull(theSystem);
IValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(theCode, theSystem, null, null);
if (result.isFound()) {
ValidateCodeResult retVal = new ValidateCodeResult(true, "Found code", result.getCodeDisplay());
return retVal;
}
}
if (vs != null) {
ValidateCodeResult result;
if (myDaoConfig.isPreExpandValueSets() && !isBuiltInValueSet && myTerminologySvc.isValueSetPreExpandedForCodeValidation(vs)) {
result = myTerminologySvc.validateCodeIsInPreExpandedValueSet(new ValidationOptions(), vs, toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
} else {
ValueSet expansion = doExpand(vs);
List<ValueSetExpansionContainsComponent> contains = expansion.getExpansion().getContains();
result = validateCodeIsInContains(contains, toStringOrNull(theSystem), toStringOrNull(theCode), theCoding, theCodeableConcept);
}
if (result != null) {
if (theDisplay != null && isNotBlank(theDisplay.getValue()) && isNotBlank(result.getDisplay())) {
if (!theDisplay.getValue().equals(result.getDisplay())) {
return new ValidateCodeResult(false, "Display for code does not match", result.getDisplay());
}
}
return result;
}
}
return new ValidateCodeResult(false, "Code not found", null);
}
private String toStringOrNull(IPrimitiveType<String> thePrimitive) {
return thePrimitive != null ? thePrimitive.getValue() : null;
}
private ValidateCodeResult validateCodeIsInContains(List<ValueSetExpansionContainsComponent> contains, String theSystem, String theCode,
Coding theCoding, CodeableConcept theCodeableConcept) {
for (ValueSetExpansionContainsComponent nextCode : contains) {
ValidateCodeResult result = validateCodeIsInContains(nextCode.getContains(), theSystem, theCode, theCoding, theCodeableConcept);
if (result != null) {
return result;
}
String system = nextCode.getSystem();
String code = nextCode.getCode();
if (isNotBlank(theCode)) {
if (theCode.equals(code) && (isBlank(theSystem) || theSystem.equals(system))) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
} else if (theCoding != null) {
if (StringUtils.equals(system, theCoding.getSystem()) && StringUtils.equals(code, theCoding.getCode())) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
} else {
for (Coding next : theCodeableConcept.getCoding()) {
if (StringUtils.equals(system, next.getSystem()) && StringUtils.equals(code, next.getCode())) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
}
}
}
return null;
} }
@Override @Override

View File

@ -0,0 +1,103 @@
package ca.uhn.fhir.jpa.dao.tx;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.api.model.ResourceVersionConflictResolutionStrategy;
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.PostConstruct;
public class HapiTransactionService {
private static final Logger ourLog = LoggerFactory.getLogger(HapiTransactionService.class);
@Autowired
private IInterceptorBroadcaster myInterceptorBroadcaster;
@Autowired
private PlatformTransactionManager myTransactionManager;
private TransactionTemplate myTxTemplate;
@PostConstruct
public void start() {
myTxTemplate = new TransactionTemplate(myTransactionManager);
}
public <T> T execute(RequestDetails theRequestDetails, TransactionCallback<T> theCallback) {
for (int i = 0; ; i++) {
try {
try {
return myTxTemplate.execute(theCallback);
} catch (MyException e) {
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else {
throw new InternalErrorException(e);
}
}
} catch (ResourceVersionConflictException e) {
ourLog.debug("Version conflict detected: {}", e.toString());
HookParams params = new HookParams()
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
ResourceVersionConflictResolutionStrategy conflictResolutionStrategy = (ResourceVersionConflictResolutionStrategy) JpaInterceptorBroadcaster.doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequestDetails, Pointcut.STORAGE_VERSION_CONFLICT, params);
if (conflictResolutionStrategy != null && conflictResolutionStrategy.isRetry()) {
if (i <= conflictResolutionStrategy.getMaxRetries()) {
continue;
}
ourLog.info("Max retries ({}) exceeded for version conflict", conflictResolutionStrategy.getMaxRetries());
}
throw e;
}
}
}
/**
* This is just an unchecked exception so that we can catch checked exceptions inside TransactionTemplate
* and rethrow them outside of it
*/
static class MyException extends RuntimeException {
public MyException(Throwable theThrowable) {
super(theThrowable);
}
}
}

View File

@ -0,0 +1,37 @@
package ca.uhn.fhir.jpa.dao.tx;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* @see HapiTransactionalAspect
* @since 5.1.0
*/
@Retention(RUNTIME)
@Target({METHOD, TYPE})
public @interface HapiTransactional {
}

View File

@ -44,6 +44,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.util.List; import java.util.List;

View File

@ -25,15 +25,24 @@ import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.IQueryParameterOr;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.DateOrListParam;
import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.NumberOrListParam;
import ca.uhn.fhir.rest.param.NumberParam; import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.QuantityOrListParam;
import ca.uhn.fhir.rest.param.QuantityParam; import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.rest.param.ReferenceOrListParam;
import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.SpecialOrListParam;
import ca.uhn.fhir.rest.param.SpecialParam; import ca.uhn.fhir.rest.param.SpecialParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.StringOrListParam;
import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
@ -56,6 +65,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import static ca.uhn.fhir.rest.api.Constants.PARAM_FILTER; import static ca.uhn.fhir.rest.api.Constants.PARAM_FILTER;
@ -111,42 +121,63 @@ public class JpaStorageServices extends BaseHapiFhirDao<IBaseResource> implement
throw new InvalidRequestException(msg); throw new InvalidRequestException(msg);
} }
for (Value nextValue : nextArgument.getValues()) { IQueryParameterOr<?> queryParam;
String value = nextValue.getValue();
IQueryParameterType param = null; switch (searchParam.getParamType()) {
switch (searchParam.getParamType()) { case NUMBER:
case NUMBER: NumberOrListParam numberOrListParam = new NumberOrListParam();
param = new NumberParam(value); for (Value value: nextArgument.getValues()) {
break; numberOrListParam.addOr(new NumberParam(value.getValue()));
case DATE: }
param = new DateParam(value); queryParam = numberOrListParam;
break; break;
case STRING: case DATE:
param = new StringParam(value); DateOrListParam dateOrListParam = new DateOrListParam();
break; for (Value value: nextArgument.getValues()) {
case TOKEN: dateOrListParam.addOr(new DateParam(value.getValue()));
param = new TokenParam(null, value); }
break; queryParam = dateOrListParam;
case REFERENCE: break;
param = new ReferenceParam(value); case STRING:
break; StringOrListParam stringOrListParam = new StringOrListParam();
case COMPOSITE: for (Value value: nextArgument.getValues()) {
throw new InvalidRequestException("Composite parameters are not yet supported in GraphQL"); stringOrListParam.addOr(new StringParam(value.getValue()));
case QUANTITY: }
param = new QuantityParam(value); queryParam = stringOrListParam;
break; break;
case SPECIAL: case TOKEN:
param = new SpecialParam().setValue(value); TokenOrListParam tokenOrListParam = new TokenOrListParam();
break; for (Value value: nextArgument.getValues()) {
case URI: tokenOrListParam.addOr(new TokenParam(value.getValue()));
break; }
case HAS: queryParam = tokenOrListParam;
break; break;
} case REFERENCE:
ReferenceOrListParam referenceOrListParam = new ReferenceOrListParam();
params.add(searchParamName, param); for (Value value: nextArgument.getValues()) {
referenceOrListParam.addOr(new ReferenceParam(value.getValue()));
}
queryParam = referenceOrListParam;
break;
case QUANTITY:
QuantityOrListParam quantityOrListParam = new QuantityOrListParam();
for (Value value: nextArgument.getValues()) {
quantityOrListParam.addOr(new QuantityParam(value.getValue()));
}
queryParam = quantityOrListParam;
break;
case SPECIAL:
SpecialOrListParam specialOrListParam = new SpecialOrListParam();
for (Value value: nextArgument.getValues()) {
specialOrListParam.addOr(new SpecialParam().setValue(value.getValue()));
}
queryParam = specialOrListParam;
break;
default:
throw new InvalidRequestException(String.format("%s parameters are not yet supported in GraphQL", searchParam.getParamType()));
} }
params.add(searchParamName, queryParam);
} }
RequestDetails requestDetails = (RequestDetails) theAppInfo; RequestDetails requestDetails = (RequestDetails) theAppInfo;

View File

@ -0,0 +1,82 @@
package ca.uhn.fhir.jpa.interceptor;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.api.model.ResourceVersionConflictResolutionStrategy;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import java.util.StringTokenizer;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.trim;
/**
* This interceptor looks for a header on incoming requests called <code>X-Retry-On-Version-Conflict</code> and
* if present, it will instruct the server to automatically retry JPA server operations that would have
* otherwise failed with a {@link ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException} (HTTP 409).
* <p>
* The format of the header is:<br/>
* <code>X-Retry-On-Version-Conflict: retry; max-retries=100</code>
* </p>
*/
@Interceptor
public class UserRequestRetryVersionConflictsInterceptor {
public static final String HEADER_NAME = "X-Retry-On-Version-Conflict";
public static final String MAX_RETRIES = "max-retries";
public static final String RETRY = "retry";
@Hook(value = Pointcut.STORAGE_VERSION_CONFLICT, order = 100)
public ResourceVersionConflictResolutionStrategy check(RequestDetails theRequestDetails) {
ResourceVersionConflictResolutionStrategy retVal = new ResourceVersionConflictResolutionStrategy();
if (theRequestDetails != null) {
for (String headerValue : theRequestDetails.getHeaders(HEADER_NAME)) {
if (isNotBlank(headerValue)) {
StringTokenizer tok = new StringTokenizer(headerValue, ";");
while (tok.hasMoreTokens()) {
String next = trim(tok.nextToken());
if (next.equals(RETRY)) {
retVal.setRetry(true);
} else if (next.startsWith(MAX_RETRIES + "=")) {
String val = trim(next.substring((MAX_RETRIES + "=").length()));
int maxRetries = Integer.parseInt(val);
maxRetries = Math.min(100, maxRetries);
retVal.setMaxRetries(maxRetries);
}
}
}
}
}
return retVal;
}
}

View File

@ -297,7 +297,8 @@ public class JpaPackageCache extends BasePackageCacheManager implements IHapiPac
} }
myPackageVersionResourceDao.save(resourceEntity); myPackageVersionResourceDao.save(resourceEntity);
String msg = "Indexing Resource[" + dirName + '/' + nextFile + "] with URL: " + defaultString(url) + "|" + defaultString(version); String resType = packageContext.getResourceType(resource);
String msg = "Indexing " + resType + " Resource[" + dirName + '/' + nextFile + "] with URL: " + defaultString(url) + "|" + defaultString(version);
getProcessingMessages(npmPackage).add(msg); getProcessingMessages(npmPackage).add(msg);
ourLog.info("Package[{}#{}] " + msg, thePackageId, packageVersionId); ourLog.info("Package[{}#{}] " + msg, thePackageId, packageVersionId);
} }

View File

@ -31,6 +31,7 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.UriParam; import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.FhirTerser; import ca.uhn.fhir.util.FhirTerser;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.gson.Gson; import com.google.gson.Gson;
@ -258,13 +259,14 @@ public class PackageInstallerSvcImpl implements IPackageInstallerSvc {
return Collections.EMPTY_LIST; return Collections.EMPTY_LIST;
} }
ArrayList<IBaseResource> resources = new ArrayList<>(); ArrayList<IBaseResource> resources = new ArrayList<>();
for (String file : pkg.getFolders().get("package").listFiles()) { List<String> filesForType = pkg.getFolders().get("package").getTypes().get(type);
if (file.contains(type)) { if (filesForType != null) {
for (String file : filesForType) {
try { try {
byte[] content = pkg.getFolders().get("package").fetchFile(file); byte[] content = pkg.getFolders().get("package").fetchFile(file);
resources.add(fhirContext.newJsonParser().parseResource(new String(content))); resources.add(fhirContext.newJsonParser().parseResource(new String(content)));
} catch (IOException e) { } catch (IOException e) {
ourLog.error("Cannot install resource of type {}: Could not fetch file {}", type, file); throw new InternalErrorException("Cannot install resource of type "+type+": Could not fetch file "+ file, e);
} }
} }
} }

View File

@ -35,9 +35,11 @@ import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.transaction.Transactional;
import java.util.HashSet; import java.util.HashSet;
import static ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster.doCallHooks; import static ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster.doCallHooks;

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.provider;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
@ -39,6 +40,8 @@ import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.ParametersUtil;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -142,21 +145,27 @@ public class BaseJpaResourceProviderValueSetDstu2 extends JpaResourceProviderDst
startRequest(theServletRequest); startRequest(theServletRequest);
try { try {
IFhirResourceDaoValueSet<ValueSet, CodingDt, CodeableConceptDt> dao = (IFhirResourceDaoValueSet<ValueSet, CodingDt, CodeableConceptDt>) getDao(); IFhirResourceDaoValueSet<ValueSet, CodingDt, CodeableConceptDt> dao = (IFhirResourceDaoValueSet<ValueSet, CodingDt, CodeableConceptDt>) getDao();
IFhirResourceDaoValueSet.ValidateCodeResult result = dao.validateCode(theValueSetIdentifier, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); IValidationSupport.CodeValidationResult result = dao.validateCode(theValueSetIdentifier, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
Parameters retVal = new Parameters(); return (Parameters) toValidateCodeResult(getContext(), result);
retVal.addParameter().setName("result").setValue(new BooleanDt(result.isResult()));
if (isNotBlank(result.getMessage())) {
retVal.addParameter().setName("message").setValue(new StringDt(result.getMessage()));
}
if (isNotBlank(result.getDisplay())) {
retVal.addParameter().setName("display").setValue(new StringDt(result.getDisplay()));
}
return retVal;
} finally { } finally {
endRequest(theServletRequest); endRequest(theServletRequest);
} }
} }
public static IBaseParameters toValidateCodeResult(FhirContext theContext, IValidationSupport.CodeValidationResult theResult) {
IBaseParameters retVal = ParametersUtil.newInstance(theContext);
ParametersUtil.addParameterToParametersBoolean(theContext, retVal, "result", theResult.isOk());
if (isNotBlank(theResult.getMessage())) {
ParametersUtil.addParameterToParametersString(theContext, retVal, "message", theResult.getMessage());
}
if (isNotBlank(theResult.getDisplay())) {
ParametersUtil.addParameterToParametersString(theContext, retVal, "display", theResult.getDisplay());
}
return retVal;
}
private static boolean moreThanOneTrue(boolean... theBooleans) { private static boolean moreThanOneTrue(boolean... theBooleans) {
boolean haveOne = false; boolean haveOne = false;
for (boolean next : theBooleans) { for (boolean next : theBooleans) {

View File

@ -25,9 +25,12 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.rest.annotation.GraphQL; import ca.uhn.fhir.rest.annotation.GraphQL;
import ca.uhn.fhir.rest.annotation.GraphQLQuery; import ca.uhn.fhir.rest.annotation.GraphQLQueryBody;
import ca.uhn.fhir.rest.annotation.GraphQLQueryUrl;
import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Initialize; import ca.uhn.fhir.rest.annotation.Initialize;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -107,9 +110,23 @@ public class GraphQLProvider {
myStorageServices = theStorageServices; myStorageServices = theStorageServices;
} }
@GraphQL @GraphQL(type=RequestTypeEnum.GET)
public String processGraphQlRequest(ServletRequestDetails theRequestDetails, @IdParam IIdType theId, @GraphQLQuery String theQuery) { public String processGraphQlGetRequest(ServletRequestDetails theRequestDetails, @IdParam IIdType theId, @GraphQLQueryUrl String queryUrl) {
if (queryUrl != null) {
return processGraphQLRequest(theRequestDetails, theId, queryUrl);
}
throw new InvalidRequestException("Unable to parse empty GraphQL expression");
}
@GraphQL(type=RequestTypeEnum.POST)
public String processGraphQlPostRequest(ServletRequestDetails theRequestDetails, @IdParam IIdType theId, @GraphQLQueryBody String queryBody) {
if (queryBody != null) {
return processGraphQLRequest(theRequestDetails, theId, queryBody);
}
throw new InvalidRequestException("Unable to parse empty GraphQL expression");
}
public String processGraphQLRequest(ServletRequestDetails theRequestDetails, IIdType theId, String theQuery) {
IGraphQLEngine engine = engineFactory.get(); IGraphQLEngine engine = engineFactory.get();
engine.setAppInfo(theRequestDetails); engine.setAppInfo(theRequestDetails);
engine.setServices(myStorageServices); engine.setServices(myStorageServices);

View File

@ -20,8 +20,10 @@ package ca.uhn.fhir.jpa.provider.dstu3;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.BaseJpaResourceProviderValueSetDstu2;
import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.OperationParam;
@ -148,16 +150,8 @@ public class BaseJpaResourceProviderValueSetDstu3 extends JpaResourceProviderDst
startRequest(theServletRequest); startRequest(theServletRequest);
try { try {
IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao(); IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao();
IFhirResourceDaoValueSet.ValidateCodeResult result = dao.validateCode(url, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); IValidationSupport.CodeValidationResult result = dao.validateCode(url, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
Parameters retVal = new Parameters(); return (Parameters) BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result);
retVal.addParameter().setName("result").setValue(new BooleanType(result.isResult()));
if (isNotBlank(result.getMessage())) {
retVal.addParameter().setName("message").setValue(new StringType(result.getMessage()));
}
if (isNotBlank(result.getDisplay())) {
retVal.addParameter().setName("display").setValue(new StringType(result.getDisplay()));
}
return retVal;
} finally { } finally {
endRequest(theServletRequest); endRequest(theServletRequest);
} }

View File

@ -20,8 +20,10 @@ package ca.uhn.fhir.jpa.provider.r4;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.BaseJpaResourceProviderValueSetDstu2;
import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.OperationParam;
@ -134,16 +136,8 @@ public class BaseJpaResourceProviderValueSetR4 extends JpaResourceProviderR4<Val
startRequest(theServletRequest); startRequest(theServletRequest);
try { try {
IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao(); IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao();
IFhirResourceDaoValueSet.ValidateCodeResult result = dao.validateCode(theValueSetUrl, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); IValidationSupport.CodeValidationResult result = dao.validateCode(theValueSetUrl, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
Parameters retVal = new Parameters(); return (Parameters) BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result);
retVal.addParameter().setName("result").setValue(new BooleanType(result.isResult()));
if (isNotBlank(result.getMessage())) {
retVal.addParameter().setName("message").setValue(new StringType(result.getMessage()));
}
if (isNotBlank(result.getDisplay())) {
retVal.addParameter().setName("display").setValue(new StringType(result.getDisplay()));
}
return retVal;
} finally { } finally {
endRequest(theServletRequest); endRequest(theServletRequest);
} }

View File

@ -20,8 +20,10 @@ package ca.uhn.fhir.jpa.provider.r5;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.BaseJpaResourceProviderValueSetDstu2;
import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.OperationParam;
@ -134,16 +136,8 @@ public class BaseJpaResourceProviderValueSetR5 extends JpaResourceProviderR5<Val
startRequest(theServletRequest); startRequest(theServletRequest);
try { try {
IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao(); IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao();
IFhirResourceDaoValueSet.ValidateCodeResult result = dao.validateCode(theValueSetUrl, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); IValidationSupport.CodeValidationResult result = dao.validateCode(theValueSetUrl, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
Parameters retVal = new Parameters(); return (Parameters) BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result);
retVal.addParameter().setName("result").setValue(new BooleanType(result.isResult()));
if (isNotBlank(result.getMessage())) {
retVal.addParameter().setName("message").setValue(new StringType(result.getMessage()));
}
if (isNotBlank(result.getDisplay())) {
retVal.addParameter().setName("display").setValue(new StringType(result.getDisplay()));
}
return retVal;
} finally { } finally {
endRequest(theServletRequest); endRequest(theServletRequest);
} }

View File

@ -30,7 +30,6 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IDao; import ca.uhn.fhir.jpa.api.dao.IDao;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.api.model.TranslationQuery; import ca.uhn.fhir.jpa.api.model.TranslationQuery;
import ca.uhn.fhir.jpa.api.model.TranslationRequest; import ca.uhn.fhir.jpa.api.model.TranslationRequest;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
@ -73,6 +72,7 @@ import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException; import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException;
import ca.uhn.fhir.jpa.util.LogicUtil;
import ca.uhn.fhir.jpa.util.ScrollableResultsIterator; import ca.uhn.fhir.jpa.util.ScrollableResultsIterator;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
@ -81,6 +81,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.util.ValidateUtil; import ca.uhn.fhir.util.ValidateUtil;
@ -105,10 +106,14 @@ import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.FullTextQuery; import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.query.dsl.BooleanJunction; import org.hibernate.search.query.dsl.BooleanJunction;
import org.hibernate.search.query.dsl.QueryBuilder; import org.hibernate.search.query.dsl.QueryBuilder;
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseCoding; import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
@ -169,6 +174,7 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNoneBlank; import static org.apache.commons.lang3.StringUtils.isNoneBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
public abstract class BaseTermReadSvcImpl implements ITermReadSvc { public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
public static final int DEFAULT_FETCH_SIZE = 250; public static final int DEFAULT_FETCH_SIZE = 250;
@ -653,16 +659,21 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
includedConcepts = theIncludeOrExclude includedConcepts = theIncludeOrExclude
.getConcept() .getConcept()
.stream() .stream()
.map(t->new VersionIndependentConcept(theIncludeOrExclude.getSystem(), t.getCode())) .map(t -> new VersionIndependentConcept(theIncludeOrExclude.getSystem(), t.getCode()))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
if (includedConcepts != null) { if (includedConcepts != null) {
int foundCount = 0; int foundCount = 0;
for (VersionIndependentConcept next : includedConcepts) { for (VersionIndependentConcept next : includedConcepts) {
LookupCodeResult lookup = myValidationSupport.lookupCode(new ValidationSupportContext(myValidationSupport), next.getSystem(), next.getCode()); String nextSystem = next.getSystem();
if (nextSystem == null) {
nextSystem = system;
}
LookupCodeResult lookup = myValidationSupport.lookupCode(new ValidationSupportContext(provideValidationSupport()), nextSystem, next.getCode());
if (lookup != null && lookup.isFound()) { if (lookup != null && lookup.isFound()) {
addOrRemoveCode(theValueSetCodeAccumulator, theAddedCodes, theAdd, next.getSystem(), next.getCode(), lookup.getCodeDisplay()); addOrRemoveCode(theValueSetCodeAccumulator, theAddedCodes, theAdd, nextSystem, next.getCode(), lookup.getCodeDisplay());
foundCount++; foundCount++;
} }
} }
@ -1244,8 +1255,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return true; return true;
} }
protected IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInPreExpandedValueSet( protected IValidationSupport.CodeValidationResult validateCodeIsInPreExpandedValueSet(
ValidationOptions theValidationOptions, ConceptValidationOptions theValidationOptions,
ValueSet theValueSet, String theSystem, String theCode, String theDisplay, Coding theCoding, CodeableConcept theCodeableConcept) { ValueSet theValueSet, String theSystem, String theCode, String theDisplay, Coding theCoding, CodeableConcept theCodeableConcept) {
ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet.hasId(), "ValueSet.id is required"); ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet.hasId(), "ValueSet.id is required");
@ -1253,7 +1264,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
List<TermValueSetConcept> concepts = new ArrayList<>(); List<TermValueSetConcept> concepts = new ArrayList<>();
if (isNotBlank(theCode)) { if (isNotBlank(theCode)) {
if (theValidationOptions.isGuessSystem()) { if (theValidationOptions.isInferSystem()) {
concepts.addAll(myValueSetConceptDao.findByValueSetResourcePidAndCode(valueSetResourcePid.getIdAsLong(), theCode)); concepts.addAll(myValueSetConceptDao.findByValueSetResourcePidAndCode(valueSetResourcePid.getIdAsLong(), theCode));
} else if (isNotBlank(theSystem)) { } else if (isNotBlank(theSystem)) {
concepts.addAll(findByValueSetResourcePidSystemAndCode(valueSetResourcePid, theSystem, theCode)); concepts.addAll(findByValueSetResourcePidSystemAndCode(valueSetResourcePid, theSystem, theCode));
@ -1271,19 +1282,40 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} }
} }
} }
} else {
return null;
} }
for (TermValueSetConcept concept : concepts) { if (theValidationOptions.isValidateDisplay() && concepts.size() > 0) {
if (isNotBlank(theDisplay) && theDisplay.equals(concept.getDisplay())) { for (TermValueSetConcept concept : concepts) {
return new IFhirResourceDaoValueSet.ValidateCodeResult(true, "Validation succeeded", concept.getDisplay()); if (isBlank(theDisplay) || isBlank(concept.getDisplay()) || theDisplay.equals(concept.getDisplay())) {
return new IValidationSupport.CodeValidationResult()
.setCode(concept.getCode())
.setDisplay(concept.getDisplay());
}
} }
return createFailureCodeValidationResult(theSystem, theCode, " - Concept Display \"" + theDisplay + "\" does not match expected \"" + concepts.get(0).getDisplay() + "\"").setDisplay(concepts.get(0).getDisplay());
} }
if (!concepts.isEmpty()) { if (!concepts.isEmpty()) {
return new IFhirResourceDaoValueSet.ValidateCodeResult(true, "Validation succeeded", concepts.get(0).getDisplay()); return new IValidationSupport.CodeValidationResult()
.setCode(concepts.get(0).getCode())
.setDisplay(concepts.get(0).getDisplay());
} }
return null; return createFailureCodeValidationResult(theSystem, theCode);
}
private CodeValidationResult createFailureCodeValidationResult(String theSystem, String theCode) {
String append = "";
return createFailureCodeValidationResult(theSystem, theCode, append);
}
private CodeValidationResult createFailureCodeValidationResult(String theSystem, String theCode, String theAppend) {
return new CodeValidationResult()
.setSeverity(IssueSeverity.ERROR)
.setMessage("Unknown code {" + theSystem + "}" + theCode + theAppend);
} }
private List<TermValueSetConcept> findByValueSetResourcePidSystemAndCode(ResourcePersistentId theResourcePid, String theSystem, String theCode) { private List<TermValueSetConcept> findByValueSetResourcePidSystemAndCode(ResourcePersistentId theResourcePid, String theSystem, String theCode) {
@ -1499,6 +1531,13 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return; return;
} }
if (source == null && theConceptMap.hasSourceCanonicalType()) {
source = theConceptMap.getSourceCanonicalType().getValueAsString();
}
if (target == null && theConceptMap.hasTargetCanonicalType()) {
target = theConceptMap.getTargetCanonicalType().getValueAsString();
}
/* /*
* For now we always delete old versions. At some point, it would be nice to allow configuration to keep old versions. * For now we always delete old versions. At some point, it would be nice to allow configuration to keep old versions.
*/ */
@ -1526,17 +1565,28 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
if (theConceptMap.hasGroup()) { if (theConceptMap.hasGroup()) {
TermConceptMapGroup termConceptMapGroup; TermConceptMapGroup termConceptMapGroup;
for (ConceptMap.ConceptMapGroupComponent group : theConceptMap.getGroup()) { for (ConceptMap.ConceptMapGroupComponent group : theConceptMap.getGroup()) {
if (isBlank(group.getSource())) {
String groupSource = group.getSource();
if (isBlank(groupSource)) {
groupSource = source;
}
if (isBlank(groupSource)) {
throw new UnprocessableEntityException("ConceptMap[url='" + theConceptMap.getUrl() + "'] contains at least one group without a value in ConceptMap.group.source"); throw new UnprocessableEntityException("ConceptMap[url='" + theConceptMap.getUrl() + "'] contains at least one group without a value in ConceptMap.group.source");
} }
if (isBlank(group.getTarget())) {
String groupTarget = group.getTarget();
if (isBlank(groupTarget)) {
groupTarget = target;
}
if (isBlank(groupTarget)) {
throw new UnprocessableEntityException("ConceptMap[url='" + theConceptMap.getUrl() + "'] contains at least one group without a value in ConceptMap.group.target"); throw new UnprocessableEntityException("ConceptMap[url='" + theConceptMap.getUrl() + "'] contains at least one group without a value in ConceptMap.group.target");
} }
termConceptMapGroup = new TermConceptMapGroup(); termConceptMapGroup = new TermConceptMapGroup();
termConceptMapGroup.setConceptMap(termConceptMap); termConceptMapGroup.setConceptMap(termConceptMap);
termConceptMapGroup.setSource(group.getSource()); termConceptMapGroup.setSource(groupSource);
termConceptMapGroup.setSourceVersion(group.getSourceVersion()); termConceptMapGroup.setSourceVersion(group.getSourceVersion());
termConceptMapGroup.setTarget(group.getTarget()); termConceptMapGroup.setTarget(groupTarget);
termConceptMapGroup.setTargetVersion(group.getTargetVersion()); termConceptMapGroup.setTargetVersion(group.getTargetVersion());
myConceptMapGroupDao.save(termConceptMapGroup); myConceptMapGroupDao.save(termConceptMapGroup);
@ -1636,6 +1686,58 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} }
} }
@Override
public CodeValidationResult validateCode(ConceptValidationOptions theOptions, IIdType theValueSetId, String theValueSetUrl, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) {
CodeableConcept codeableConcept = toCanonicalCodeableConcept(theCodeableConcept);
boolean haveCodeableConcept = codeableConcept != null && codeableConcept.getCoding().size() > 0;
Coding coding = toCanonicalCoding(theCoding);
boolean haveCoding = coding != null && coding.isEmpty() == false;
boolean haveCode = theCode != null && theCode.isEmpty() == false;
if (!haveCodeableConcept && !haveCoding && !haveCode) {
throw new InvalidRequestException("No code, coding, or codeableConcept provided to validate");
}
if (!LogicUtil.multiXor(haveCodeableConcept, haveCoding, haveCode)) {
throw new InvalidRequestException("$validate-code can only validate (system AND code) OR (coding) OR (codeableConcept)");
}
boolean haveIdentifierParam = isNotBlank(theValueSetUrl);
String valueSetUrl;
if (theValueSetId != null) {
IBaseResource valueSet = myDaoRegistry.getResourceDao("ValueSet").read(theValueSetId);
valueSetUrl = CommonCodeSystemsTerminologyService.getValueSetUrl(valueSet);
} else if (haveIdentifierParam) {
valueSetUrl = theValueSetUrl;
} else {
throw new InvalidRequestException("Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate.");
}
ValidationSupportContext validationContext = new ValidationSupportContext(provideValidationSupport());
String code = theCode;
String system = theSystem;
String display = theDisplay;
if (haveCodeableConcept) {
for (int i = 0; i < codeableConcept.getCoding().size(); i++) {
Coding nextCoding = codeableConcept.getCoding().get(i);
CodeValidationResult nextValidation = validateCode(validationContext, theOptions, nextCoding.getSystem(), nextCoding.getCode(), nextCoding.getDisplay(), valueSetUrl);
if (nextValidation.isOk() || i == codeableConcept.getCoding().size() - 1) {
return nextValidation;
}
}
} else if (haveCoding) {
system = coding.getSystem();
code = coding.getCode();
display = coding.getDisplay();
}
return validateCode(validationContext, theOptions, system, code, display, valueSetUrl);
}
private boolean isNotSafeToPreExpandValueSets() { private boolean isNotSafeToPreExpandValueSets() {
return myDeferredStorageSvc != null && !myDeferredStorageSvc.isStorageQueueEmpty(); return myDeferredStorageSvc != null && !myDeferredStorageSvc.isStorageQueueEmpty();
} }
@ -1997,7 +2099,36 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return null; return null;
} }
Optional<VersionIndependentConcept> validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theValidationOptions, String theValueSetUrl, String theCodeSystem, String theCode) {
@CoverageIgnore
@Override
public IValidationSupport.CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
invokeRunnableForUnitTest();
if (isNotBlank(theValueSetUrl)) {
return validateCodeInValueSet(theValidationSupportContext, theOptions, theValueSetUrl, theCodeSystem, theCode, theDisplay);
}
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
Optional<VersionIndependentConcept> codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> new VersionIndependentConcept(theCodeSystem, c.getCode())));
if (codeOpt != null && codeOpt.isPresent()) {
VersionIndependentConcept code = codeOpt.get();
if (!theOptions.isValidateDisplay() || (isNotBlank(code.getDisplay()) && isNotBlank(theDisplay) && code.getDisplay().equals(theDisplay))) {
return new CodeValidationResult()
.setCode(code.getCode())
.setDisplay(code.getDisplay());
} else {
return createFailureCodeValidationResult(theCodeSystem, theCode, " - Concept Display \"" + code.getDisplay() + "\" does not match expected \"" + code.getDisplay() + "\"").setDisplay(code.getDisplay());
}
}
return createFailureCodeValidationResult(theCodeSystem, theCode);
}
IValidationSupport.CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theValidationOptions, String theValueSetUrl, String theCodeSystem, String theCode, String theDisplay) {
IBaseResource valueSet = theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl); IBaseResource valueSet = theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl);
// If we don't have a PID, this came from some source other than the JPA // If we don't have a PID, this came from some source other than the JPA
@ -2006,24 +2137,26 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
Long pid = IDao.RESOURCE_PID.get((IAnyResource) valueSet); Long pid = IDao.RESOURCE_PID.get((IAnyResource) valueSet);
if (pid != null) { if (pid != null) {
if (isValueSetPreExpandedForCodeValidation(valueSet)) { if (isValueSetPreExpandedForCodeValidation(valueSet)) {
IFhirResourceDaoValueSet.ValidateCodeResult outcome = validateCodeIsInPreExpandedValueSet(new ValidationOptions(), valueSet, theCodeSystem, theCode, null, null, null); return validateCodeIsInPreExpandedValueSet(theValidationOptions, valueSet, theCodeSystem, theCode, null, null, null);
if (outcome != null && outcome.isResult()) {
return Optional.of(new VersionIndependentConcept(theCodeSystem, theCode));
}
} }
} }
} }
ValueSet canonicalValueSet = toCanonicalValueSet(valueSet); CodeValidationResult retVal = null;
VersionIndependentConcept wantConcept = new VersionIndependentConcept(theCodeSystem, theCode); if (valueSet != null) {
ValueSetExpansionOptions expansionOptions = new ValueSetExpansionOptions() retVal = new InMemoryTerminologyServerValidationSupport(myContext).validateCodeInValueSet(theValidationSupportContext, theValidationOptions, theCodeSystem, theCode, theDisplay, valueSet);
.setFailOnMissingCodeSystem(false); } else {
String append = " - Unable to locate ValueSet[" + theValueSetUrl + "]";
retVal = createFailureCodeValidationResult(theCodeSystem, theCode, append);
}
if (retVal == null) {
String append = " - Unable to expand ValueSet[" + theValueSetUrl + "]";
retVal = createFailureCodeValidationResult(theCodeSystem, theCode, append);
}
return retVal;
List<VersionIndependentConcept> expansionOutcome = expandValueSetAndReturnVersionIndependentConcepts(expansionOptions, canonicalValueSet, wantConcept);
return expansionOutcome
.stream()
.filter(t -> (theValidationOptions.isInferSystem() || t.getSystem().equals(theCodeSystem)) && t.getCode().equals(theCode))
.findFirst();
} }
@Override @Override
@ -2148,6 +2281,12 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return false; return false;
} }
@Nullable
protected abstract Coding toCanonicalCoding(@Nullable IBaseDatatype theCoding);
@Nullable
protected abstract CodeableConcept toCanonicalCodeableConcept(@Nullable IBaseDatatype theCodeableConcept);
public static class Job implements HapiJob { public static class Job implements HapiJob {
@Autowired @Autowired
private ITermReadSvc myTerminologySvc; private ITermReadSvc myTerminologySvc;

View File

@ -20,18 +20,23 @@ package ca.uhn.fhir.jpa.term;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.util.VersionIndependentConcept; import ca.uhn.fhir.util.VersionIndependentConcept;
import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.utilities.validation.ValidationOptions; import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -132,8 +137,34 @@ public class TermReadSvcDstu2 extends BaseTermReadSvcImpl {
return retVal; return retVal;
} }
@Nullable
@Override @Override
public IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInPreExpandedValueSet(ValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) { protected Coding toCanonicalCoding(@Nullable IBaseDatatype theCoding) {
Coding retVal = null;
if (theCoding != null) {
CodingDt coding = (CodingDt) theCoding;
retVal = new Coding(coding.getSystem(), coding.getCode(), coding.getDisplay());
}
return retVal;
}
@Nullable
@Override
protected CodeableConcept toCanonicalCodeableConcept(@Nullable IBaseDatatype theCodeableConcept) {
CodeableConcept outcome = null;
if (theCodeableConcept != null) {
outcome = new CodeableConcept();
CodeableConceptDt cc = (CodeableConceptDt) theCodeableConcept;
outcome.setText(cc.getText());
for (CodingDt next : cc.getCoding()) {
outcome.addCoding(toCanonicalCoding(next));
}
}
return outcome;
}
@Override
public CodeValidationResult validateCodeIsInPreExpandedValueSet(ConceptValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View File

@ -12,6 +12,8 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.ValidateUtil; import ca.uhn.fhir.util.ValidateUtil;
import ca.uhn.fhir.util.VersionIndependentConcept; import ca.uhn.fhir.util.VersionIndependentConcept;
import org.hl7.fhir.convertors.VersionConvertor_30_40;
import org.hl7.fhir.convertors.VersionConvertor_40_50;
import org.hl7.fhir.convertors.conv30_40.CodeSystem30_40; import org.hl7.fhir.convertors.conv30_40.CodeSystem30_40;
import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.CodeableConcept;
@ -26,6 +28,7 @@ import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nullable;
import java.util.Optional; import java.util.Optional;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -103,6 +106,20 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
return CodeSystem30_40.convertCodeSystem((CodeSystem)theCodeSystem); return CodeSystem30_40.convertCodeSystem((CodeSystem)theCodeSystem);
} }
@Override
@Nullable
protected org.hl7.fhir.r4.model.Coding toCanonicalCoding(IBaseDatatype theCoding) {
return VersionConvertor_30_40.convertCoding((org.hl7.fhir.dstu3.model.Coding) theCoding);
}
@Override
@Nullable
protected org.hl7.fhir.r4.model.CodeableConcept toCanonicalCodeableConcept(IBaseDatatype theCoding) {
return VersionConvertor_30_40.convertCodeableConcept((org.hl7.fhir.dstu3.model.CodeableConcept) theCoding);
}
@Override @Override
public void expandValueSet(ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) { public void expandValueSet(ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) {
ValueSet valueSetToExpand = (ValueSet) theValueSetToExpand; ValueSet valueSetToExpand = (ValueSet) theValueSetToExpand;
@ -130,35 +147,6 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
return valueSetR4; return valueSetR4;
} }
@CoverageIgnore
@Override
public IValidationSupport.CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
Optional<VersionIndependentConcept> codeOpt = Optional.empty();
boolean haveValidated = false;
if (isNotBlank(theValueSetUrl)) {
codeOpt = super.validateCodeInValueSet(theValidationSupportContext, theOptions, theValueSetUrl, theCodeSystem, theCode);
haveValidated = true;
}
if (!haveValidated) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> new VersionIndependentConcept(theCodeSystem, c.getCode())));
}
if (codeOpt != null && codeOpt.isPresent()) {
VersionIndependentConcept code = codeOpt.get();
IValidationSupport.CodeValidationResult retVal = new IValidationSupport.CodeValidationResult()
.setCode(code.getCode());
return retVal;
}
return new IValidationSupport.CodeValidationResult()
.setSeverity(IssueSeverity.ERROR)
.setMessage("Unknown code {" + theCodeSystem + "}" + theCode);
}
@Override @Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) {
return super.lookupCode(theSystem, theCode); return super.lookupCode(theSystem, theCode);
@ -170,7 +158,7 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
} }
@Override @Override
public IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInPreExpandedValueSet(ValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) { public IValidationSupport.CodeValidationResult validateCodeIsInPreExpandedValueSet(ConceptValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) {
ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null"); ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null");
ValueSet valueSet = (ValueSet) theValueSet; ValueSet valueSet = (ValueSet) theValueSet;
org.hl7.fhir.r4.model.ValueSet valueSetR4 = convertValueSet(valueSet); org.hl7.fhir.r4.model.ValueSet valueSetR4 = convertValueSet(valueSet);

View File

@ -5,28 +5,19 @@ import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4; import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4;
import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException; import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException;
import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.VersionIndependentConcept;
import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
import javax.transaction.Transactional; import javax.transaction.Transactional;
import java.util.Optional;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/* /*
* #%L * #%L
@ -98,50 +89,29 @@ public class TermReadSvcR4 extends BaseTermReadSvcImpl implements ITermReadSvcR4
return (CodeSystem) theCodeSystem; return (CodeSystem) theCodeSystem;
} }
@CoverageIgnore
@Override
public IValidationSupport.CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
invokeRunnableForUnitTest();
Optional<VersionIndependentConcept> codeOpt = Optional.empty();
boolean haveValidated = false;
if (isNotBlank(theValueSetUrl)) {
codeOpt = super.validateCodeInValueSet(theValidationSupportContext, theOptions, theValueSetUrl, theCodeSystem, theCode);
haveValidated = true;
}
if (!haveValidated) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> new VersionIndependentConcept(theCodeSystem, c.getCode())));
}
if (codeOpt != null && codeOpt.isPresent()) {
VersionIndependentConcept code = codeOpt.get();
IValidationSupport.CodeValidationResult retVal = new IValidationSupport.CodeValidationResult()
.setCode(code.getCode());
return retVal;
}
return new IValidationSupport.CodeValidationResult()
.setSeverity(IssueSeverity.ERROR)
.setMessage("Unknown code {" + theCodeSystem + "}" + theCode);
}
@Override @Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) {
return super.lookupCode(theSystem, theCode); return super.lookupCode(theSystem, theCode);
} }
@Override @Override
public IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInPreExpandedValueSet(ValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) { public IValidationSupport.CodeValidationResult validateCodeIsInPreExpandedValueSet(ConceptValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) {
ValueSet valueSet = (ValueSet) theValueSet; ValueSet valueSet = (ValueSet) theValueSet;
Coding coding = (Coding) theCoding; Coding coding = toCanonicalCoding(theCoding);
CodeableConcept codeableConcept = (CodeableConcept) theCodeableConcept; CodeableConcept codeableConcept = toCanonicalCodeableConcept(theCodeableConcept);
return super.validateCodeIsInPreExpandedValueSet(theOptions, valueSet, theSystem, theCode, theDisplay, coding, codeableConcept); return super.validateCodeIsInPreExpandedValueSet(theOptions, valueSet, theSystem, theCode, theDisplay, coding, codeableConcept);
} }
@Override
protected Coding toCanonicalCoding(IBaseDatatype theCoding) {
return (Coding) theCoding;
}
@Override
protected CodeableConcept toCanonicalCodeableConcept(IBaseDatatype theCodeableConcept) {
return (CodeableConcept) theCodeableConcept;
}
@Override @Override
public boolean isValueSetPreExpandedForCodeValidation(IBaseResource theValueSet) { public boolean isValueSetPreExpandedForCodeValidation(IBaseResource theValueSet) {
ValueSet valueSet = (ValueSet) theValueSet; ValueSet valueSet = (ValueSet) theValueSet;

View File

@ -6,17 +6,15 @@ import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5; import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5;
import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException; import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException;
import ca.uhn.fhir.util.ValidateUtil; import ca.uhn.fhir.util.ValidateUtil;
import ca.uhn.fhir.util.VersionIndependentConcept; import org.hl7.fhir.convertors.VersionConvertor_40_50;
import org.hl7.fhir.convertors.conv40_50.CodeSystem40_50; import org.hl7.fhir.convertors.conv40_50.CodeSystem40_50;
import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.CodeableConcept; import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.Coding; import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.model.ValueSet;
@ -24,13 +22,9 @@ import org.hl7.fhir.utilities.TerminologyServiceOptions;
import org.hl7.fhir.utilities.validation.ValidationOptions; import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nullable;
import javax.transaction.Transactional; import javax.transaction.Transactional;
import java.util.Optional;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/* /*
* #%L * #%L
@ -86,57 +80,18 @@ public class TermReadSvcR5 extends BaseTermReadSvcImpl implements IValidationSup
return org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet(valueSetR5); return org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet(valueSetR5);
} }
@Override
public IValidationSupport.CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
Optional<VersionIndependentConcept> codeOpt = Optional.empty();
boolean haveValidated = false;
if (isNotBlank(theValueSetUrl)) {
codeOpt = super.validateCodeInValueSet(theValidationSupportContext, theOptions, theValueSetUrl, theCodeSystem, theCode);
haveValidated = true;
}
if (!haveValidated) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> new VersionIndependentConcept(theCodeSystem, c.getCode())));
}
if (codeOpt != null && codeOpt.isPresent()) {
VersionIndependentConcept code = codeOpt.get();
ConceptDefinitionComponent def = new ConceptDefinitionComponent();
def.setCode(code.getCode());
IValidationSupport.CodeValidationResult retVal = new IValidationSupport.CodeValidationResult()
.setCode(code.getCode());
return retVal;
}
return new IValidationSupport.CodeValidationResult()
.setSeverity(IssueSeverity.ERROR)
.setCode("Unknown code {" + theCodeSystem + "}" + theCode);
}
@Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) {
return super.lookupCode(theSystem, theCode);
}
@Override @Override
public FhirContext getFhirContext() { public FhirContext getFhirContext() {
return myContext; return myContext;
} }
@Override @Override
public IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInPreExpandedValueSet(ValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) { public CodeValidationResult validateCodeIsInPreExpandedValueSet(ConceptValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) {
ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null"); ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null");
ValueSet valueSet = (ValueSet) theValueSet; ValueSet valueSet = (ValueSet) theValueSet;
org.hl7.fhir.r4.model.ValueSet valueSetR4 = toCanonicalValueSet(valueSet); org.hl7.fhir.r4.model.ValueSet valueSetR4 = toCanonicalValueSet(valueSet);
Coding coding = (Coding) theCoding; org.hl7.fhir.r4.model.Coding codingR4 = toCanonicalCoding(theCoding);
org.hl7.fhir.r4.model.Coding codingR4 = null;
if (coding != null) {
codingR4 = new org.hl7.fhir.r4.model.Coding(coding.getSystem(), coding.getCode(), coding.getDisplay());
}
CodeableConcept codeableConcept = (CodeableConcept) theCodeableConcept; CodeableConcept codeableConcept = (CodeableConcept) theCodeableConcept;
org.hl7.fhir.r4.model.CodeableConcept codeableConceptR4 = null; org.hl7.fhir.r4.model.CodeableConcept codeableConceptR4 = null;
@ -147,9 +102,22 @@ public class TermReadSvcR5 extends BaseTermReadSvcImpl implements IValidationSup
} }
} }
return super.validateCodeIsInPreExpandedValueSet(new TerminologyServiceOptions(), valueSetR4, theSystem, theCode, theDisplay, codingR4, codeableConceptR4); return super.validateCodeIsInPreExpandedValueSet(theOptions, valueSetR4, theSystem, theCode, theDisplay, codingR4, codeableConceptR4);
} }
@Override
@Nullable
protected org.hl7.fhir.r4.model.Coding toCanonicalCoding(IBaseDatatype theCoding) {
return VersionConvertor_40_50.convertCoding((Coding) theCoding);
}
@Override
@Nullable
protected org.hl7.fhir.r4.model.CodeableConcept toCanonicalCodeableConcept(IBaseDatatype theCoding) {
return VersionConvertor_40_50.convertCodeableConcept((CodeableConcept) theCoding);
}
@Override @Override
protected org.hl7.fhir.r4.model.ValueSet toCanonicalValueSet(IBaseResource theValueSet) throws org.hl7.fhir.exceptions.FHIRException { protected org.hl7.fhir.r4.model.ValueSet toCanonicalValueSet(IBaseResource theValueSet) throws org.hl7.fhir.exceptions.FHIRException {
return org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet((ValueSet) theValueSet); return org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet((ValueSet) theValueSet);

View File

@ -1,19 +1,22 @@
package ca.uhn.fhir.jpa.term.api; package ca.uhn.fhir.jpa.term.api;
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.api.model.TranslationRequest; import ca.uhn.fhir.jpa.api.model.TranslationRequest;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement; import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement;
import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget; import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.IValueSetConceptAccumulator; import ca.uhn.fhir.jpa.term.IValueSetConceptAccumulator;
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.util.VersionIndependentConcept; import ca.uhn.fhir.util.VersionIndependentConcept;
import org.hl7.fhir.instance.model.api.IBaseCoding; import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.ConceptMap;
@ -105,7 +108,12 @@ public interface ITermReadSvc extends IValidationSupport {
/** /**
* Version independent * Version independent
*/ */
IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInPreExpandedValueSet(ValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept); CodeValidationResult validateCode(ConceptValidationOptions theOptions, IIdType theValueSetId, String theValueSetUrl, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept);
/**
* Version independent
*/
CodeValidationResult validateCodeIsInPreExpandedValueSet(ConceptValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept);
boolean isValueSetPreExpandedForCodeValidation(ValueSet theValueSet); boolean isValueSetPreExpandedForCodeValidation(ValueSet theValueSet);
@ -114,5 +122,4 @@ public interface ITermReadSvc extends IValidationSupport {
*/ */
boolean isValueSetPreExpandedForCodeValidation(IBaseResource theValueSet); boolean isValueSetPreExpandedForCodeValidation(IBaseResource theValueSet);
} }

View File

@ -66,13 +66,13 @@ public class JpaValidationSupportChain extends ValidationSupportChain {
@PostConstruct @PostConstruct
public void postConstruct() { public void postConstruct() {
addValidationSupport(new CommonCodeSystemsTerminologyService(myFhirContext));
addValidationSupport(myDefaultProfileValidationSupport); addValidationSupport(myDefaultProfileValidationSupport);
addValidationSupport(myJpaValidationSupport); addValidationSupport(myJpaValidationSupport);
addValidationSupport(myTerminologyService); addValidationSupport(myTerminologyService);
addValidationSupport(new SnapshotGeneratingValidationSupport(myFhirContext)); addValidationSupport(new SnapshotGeneratingValidationSupport(myFhirContext));
addValidationSupport(new InMemoryTerminologyServerValidationSupport(myFhirContext)); addValidationSupport(new InMemoryTerminologyServerValidationSupport(myFhirContext));
addValidationSupport(myNpmJpaValidationSupport); addValidationSupport(myNpmJpaValidationSupport);
addValidationSupport(new CommonCodeSystemsTerminologyService(myFhirContext));
} }
} }

View File

@ -7,6 +7,7 @@ import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.IInstanceValidatorModule; import ca.uhn.fhir.validation.IInstanceValidatorModule;
import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhir.validation.ResultSeverityEnum;
import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder; import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder;
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSource;
import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.H2Dialect;

View File

@ -56,6 +56,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
private Exception myLastStackTrace; private Exception myLastStackTrace;
@Override
@Bean @Bean
public IBatchJobSubmitter batchJobSubmitter() { public IBatchJobSubmitter batchJobSubmitter() {
return new BatchJobSubmitterImpl(); return new BatchJobSubmitterImpl();
@ -118,7 +119,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
retVal.setDriver(new org.h2.Driver()); retVal.setDriver(new org.h2.Driver());
retVal.setUrl("jdbc:h2:mem:testdb_r4"); retVal.setUrl("jdbc:h2:mem:testdb_r4");
retVal.setMaxWaitMillis(10000); retVal.setMaxWaitMillis(30000);
retVal.setUsername(""); retVal.setUsername("");
retVal.setPassword(""); retVal.setPassword("");
retVal.setMaxTotal(ourMaxThreads); retVal.setMaxTotal(ourMaxThreads);
@ -126,7 +127,8 @@ public class TestR4Config extends BaseJavaConfigR4 {
SLF4JLogLevel level = SLF4JLogLevel.INFO; SLF4JLogLevel level = SLF4JLogLevel.INFO;
DataSource dataSource = ProxyDataSourceBuilder DataSource dataSource = ProxyDataSourceBuilder
.create(retVal) .create(retVal)
.logSlowQueryBySlf4j(10, TimeUnit.SECONDS) // .logQueryBySlf4j(level)
.logSlowQueryBySlf4j(10, TimeUnit.SECONDS, level)
.beforeQuery(new BlockLargeNumbersOfParamsListener()) .beforeQuery(new BlockLargeNumbersOfParamsListener())
.afterQuery(captureQueriesListener()) .afterQuery(captureQueriesListener())
.afterQuery(new CurrentThreadCaptureQueriesListener()) .afterQuery(new CurrentThreadCaptureQueriesListener())

View File

@ -9,8 +9,8 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.api.model.ExpungeOptions; import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.bulk.api.IBulkDataExportSvc; import ca.uhn.fhir.jpa.bulk.api.IBulkDataExportSvc;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.model.util.JpaConstants;
@ -22,6 +22,8 @@ import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc; import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionLoader;
import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry;
import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener; import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener;
import ca.uhn.fhir.jpa.util.MemoryCacheService; import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle;
@ -29,6 +31,7 @@ import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.test.BaseTest; import ca.uhn.fhir.test.BaseTest;
import ca.uhn.fhir.test.utilities.LoggingExtension; import ca.uhn.fhir.test.utilities.LoggingExtension;
@ -59,7 +62,6 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
@ -128,6 +130,10 @@ public abstract class BaseJpaTest extends BaseTest {
@Autowired @Autowired
protected IPartitionLookupSvc myPartitionConfigSvc; protected IPartitionLookupSvc myPartitionConfigSvc;
@Autowired @Autowired
protected SubscriptionRegistry mySubscriptionRegistry;
@Autowired
protected SubscriptionLoader mySubscriptionLoader;
@Autowired
private IdHelperService myIdHelperService; private IdHelperService myIdHelperService;
@Autowired @Autowired
private MemoryCacheService myMemoryCacheService; private MemoryCacheService myMemoryCacheService;
@ -419,6 +425,23 @@ public abstract class BaseJpaTest extends BaseTest {
return retVal.toArray(new String[0]); return retVal.toArray(new String[0]);
} }
protected void waitForActivatedSubscriptionCount(int theSize) throws Exception {
for (int i = 0; ; i++) {
if (i == 10) {
fail("Failed to init subscriptions");
}
try {
mySubscriptionLoader.doSyncSubscriptionsForUnitTest();
break;
} catch (ResourceVersionConflictException e) {
Thread.sleep(250);
}
}
TestUtil.waitForSize(theSize, () -> mySubscriptionRegistry.size());
Thread.sleep(500);
}
@BeforeAll @BeforeAll
public static void beforeClassRandomizeLocale() { public static void beforeClassRandomizeLocale() {
randomizeLocale(); randomizeLocale();

View File

@ -590,19 +590,27 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
@Test @Test
public void testDeleteFailsIfIncomingLinks() { public void testDeleteFailsIfIncomingLinks() {
String methodName = "testDeleteFailsIfIncomingLinks"; String methodName = "testDeleteFailsIfIncomingLinks";
SearchParameterMap map;
List<IIdType> found;
Organization org = new Organization(); Organization org = new Organization();
org.setName(methodName); org.setName(methodName);
IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless();
map = SearchParameterMap.newSynchronous();
map.add("_id", new StringParam(orgId.getIdPart()));
map.addRevInclude(new Include("*"));
found = toUnqualifiedVersionlessIds(myOrganizationDao.search(map));
assertThat(found, contains(orgId));
Patient patient = new Patient(); Patient patient = new Patient();
patient.addName().addFamily(methodName); patient.addName().addFamily(methodName);
patient.getManagingOrganization().setReference(orgId); patient.getManagingOrganization().setReference(orgId);
IIdType patId = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); IIdType patId = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
SearchParameterMap map = new SearchParameterMap();
map.add("_id", new StringParam(orgId.getIdPart())); map.add("_id", new StringParam(orgId.getIdPart()));
map.addRevInclude(new Include("*")); map.addRevInclude(new Include("*"));
List<IIdType> found = toUnqualifiedVersionlessIds(myOrganizationDao.search(map)); found = toUnqualifiedVersionlessIds(myOrganizationDao.search(map));
assertThat(found, contains(orgId, patId)); assertThat(found, contains(orgId, patId));
try { try {
@ -613,9 +621,21 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
} }
map = SearchParameterMap.newSynchronous();
map.add("_id", new StringParam(orgId.getIdPart()));
map.addRevInclude(new Include("*"));
ourLog.info("***** About to perform search");
found = toUnqualifiedVersionlessIds(myOrganizationDao.search(map));
runInTransaction(()->{
ourLog.info("Resources:\n * {}", myResourceTableDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
});
assertThat(found.toString(), found, contains(orgId, patId));
myPatientDao.delete(patId, mySrd); myPatientDao.delete(patId, mySrd);
map = new SearchParameterMap(); map = SearchParameterMap.newSynchronous();
map.add("_id", new StringParam(orgId.getIdPart())); map.add("_id", new StringParam(orgId.getIdPart()));
map.addRevInclude(new Include("*")); map.addRevInclude(new Include("*"));
found = toUnqualifiedVersionlessIds(myOrganizationDao.search(map)); found = toUnqualifiedVersionlessIds(myOrganizationDao.search(map));
@ -623,7 +643,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
myOrganizationDao.delete(orgId, mySrd); myOrganizationDao.delete(orgId, mySrd);
map = new SearchParameterMap(); map = SearchParameterMap.newSynchronous();
map.add("_id", new StringParam(orgId.getIdPart())); map.add("_id", new StringParam(orgId.getIdPart()));
map.addRevInclude(new Include("*")); map.addRevInclude(new Include("*"));
found = toUnqualifiedVersionlessIds(myOrganizationDao.search(map)); found = toUnqualifiedVersionlessIds(myOrganizationDao.search(map));

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.jpa.dao.dstu2; package ca.uhn.fhir.jpa.dao.dstu2;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.model.dstu2.resource.ValueSet; import ca.uhn.fhir.model.dstu2.resource.ValueSet;
@ -8,9 +8,7 @@ import ca.uhn.fhir.model.primitive.CodeDt;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -40,30 +38,42 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
} }
@Test @Test
public void testValidateCodeOperationByCodeAndSystemBad() { public void testValidateCodeOperationByCodeAndSystemBadCode() {
UriDt valueSetIdentifier = null; UriDt valueSetIdentifier = new UriDt("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
IdDt id = null; IdDt id = null;
CodeDt code = new CodeDt("8450-9-XXX"); CodeDt code = new CodeDt("8450-9-XXX");
UriDt system = new UriDt("http://loinc.org"); UriDt system = new UriDt("http://loinc.org");
StringDt display = null; StringDt display = null;
CodingDt coding = null; CodingDt coding = null;
CodeableConceptDt codeableConcept = null; CodeableConceptDt codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertFalse(result.isResult()); assertFalse(result.isOk());
} }
@Test @Test
public void testValidateCodeOperationByCodeAndSystemGood() { public void testValidateCodeOperationByCodeAndSystemBadSystem() {
UriDt valueSetIdentifier = null; UriDt valueSetIdentifier = new UriDt("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
IdDt id = null;
CodeDt code = new CodeDt("8450-9-XXX");
UriDt system = new UriDt("http://zzz");
StringDt display = null;
CodingDt coding = null;
CodeableConceptDt codeableConcept = null;
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertFalse(result.isOk());
}
@Test
public void testValidateCodeOperationByIdentifierCodeInCsButNotInVs() {
UriDt valueSetIdentifier = new UriDt("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
IdDt id = null; IdDt id = null;
CodeDt code = new CodeDt("8450-9"); CodeDt code = new CodeDt("8450-9");
UriDt system = new UriDt("http://loinc.org"); UriDt system = new UriDt("http://loinc.org");
StringDt display = null; StringDt display = null;
CodingDt coding = null; CodingDt coding = null;
CodeableConceptDt codeableConcept = null; CodeableConceptDt codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertFalse(result.isOk());
assertEquals("Systolic blood pressure--expiration", result.getDisplay());
} }
@Test @Test
@ -75,8 +85,8 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
StringDt display = null; StringDt display = null;
CodingDt coding = null; CodingDt coding = null;
CodeableConceptDt codeableConcept = null; CodeableConceptDt codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -89,9 +99,10 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
StringDt display = new StringDt("Systolic blood pressure at First encounterXXXX"); StringDt display = new StringDt("Systolic blood pressure at First encounterXXXX");
CodingDt coding = null; CodingDt coding = null;
CodeableConceptDt codeableConcept = null; CodeableConceptDt codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertFalse(result.isResult()); assertFalse(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
assertEquals("Concept Display \"Systolic blood pressure at First encounterXXXX\" does not match expected \"Systolic blood pressure at First encounter\"", result.getMessage());
} }
@Test @Test
@ -103,8 +114,8 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
StringDt display = new StringDt("Systolic blood pressure at First encounter"); StringDt display = new StringDt("Systolic blood pressure at First encounter");
CodingDt coding = null; CodingDt coding = null;
CodeableConceptDt codeableConcept = null; CodeableConceptDt codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -117,8 +128,8 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
StringDt display = null; StringDt display = null;
CodingDt coding = null; CodingDt coding = null;
CodeableConceptDt codeableConcept = new CodeableConceptDt("http://loinc.org", "11378-7"); CodeableConceptDt codeableConcept = new CodeableConceptDt("http://loinc.org", "11378-7");
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -131,8 +142,8 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
StringDt display = null; StringDt display = null;
CodingDt coding = null; CodingDt coding = null;
CodeableConceptDt codeableConcept = null; CodeableConceptDt codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }

View File

@ -28,6 +28,7 @@ import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptDao; import ca.uhn.fhir.jpa.dao.data.ITermConceptDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptMapDao; import ca.uhn.fhir.jpa.dao.data.ITermConceptMapDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptMapGroupElementTargetDao; import ca.uhn.fhir.jpa.dao.data.ITermConceptMapGroupElementTargetDao;
import ca.uhn.fhir.jpa.dao.data.ITermValueSetDao;
import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest; import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig;
@ -340,6 +341,8 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
private IValidationSupport myJpaValidationSupportChainDstu3; private IValidationSupport myJpaValidationSupportChainDstu3;
@Autowired @Autowired
private IBulkDataExportSvc myBulkDataExportSvc; private IBulkDataExportSvc myBulkDataExportSvc;
@Autowired
protected ITermValueSetDao myTermValueSetDao;
@AfterEach() @AfterEach()
public void afterCleanupDao() { public void afterCleanupDao() {

View File

@ -1,9 +1,9 @@
package ca.uhn.fhir.jpa.dao.dstu3; package ca.uhn.fhir.jpa.dao.dstu3;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeType; import org.hl7.fhir.dstu3.model.CodeType;
import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.CodeableConcept;
@ -14,7 +14,6 @@ import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -52,6 +51,51 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
} }
@Test
public void testExpandValueSetWithIso3166() throws IOException {
ValueSet vs = loadResourceFromClasspath(ValueSet.class, "/dstu3/nl/LandISOCodelijst-2.16.840.1.113883.2.4.3.11.60.40.2.20.5.2--20171231000000.json");
myValueSetDao.create(vs);
runInTransaction(() -> {
TermValueSet vsEntity = myTermValueSetDao.findByUrl("http://decor.nictiz.nl/fhir/ValueSet/2.16.840.1.113883.2.4.3.11.60.40.2.20.5.2--20171231000000").orElseThrow(() -> new IllegalStateException());
assertEquals(TermValueSetPreExpansionStatusEnum.NOT_EXPANDED, vsEntity.getExpansionStatus());
});
IValidationSupport.CodeValidationResult validationOutcome;
UriType vsIdentifier = new UriType("http://decor.nictiz.nl/fhir/ValueSet/2.16.840.1.113883.2.4.3.11.60.40.2.20.5.2--20171231000000");
CodeType code = new CodeType();
CodeType system = new CodeType("urn:iso:std:iso:3166");
// Validate good
code.setValue("NL");
validationOutcome = myValueSetDao.validateCode(vsIdentifier, null, code, system, null, null, null, mySrd);
assertEquals(true, validationOutcome.isOk());
// Validate bad
code.setValue("QQ");
validationOutcome = myValueSetDao.validateCode(vsIdentifier, null, code, system, null, null, null, mySrd);
assertEquals(false, validationOutcome.isOk());
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
runInTransaction(() -> {
TermValueSet vsEntity = myTermValueSetDao.findByUrl("http://decor.nictiz.nl/fhir/ValueSet/2.16.840.1.113883.2.4.3.11.60.40.2.20.5.2--20171231000000").orElseThrow(() -> new IllegalStateException());
assertEquals(TermValueSetPreExpansionStatusEnum.EXPANDED, vsEntity.getExpansionStatus());
});
// Validate good
code.setValue("NL");
validationOutcome = myValueSetDao.validateCode(vsIdentifier, null, code, system, null, null, null, mySrd);
assertEquals(true, validationOutcome.isOk());
// Validate bad
code.setValue("QQ");
validationOutcome = myValueSetDao.validateCode(vsIdentifier, null, code, system, null, null, null, mySrd);
assertEquals(false, validationOutcome.isOk());
}
@Test @Test
@Disabled @Disabled
public void testBuiltInValueSetFetchAndExpand() { public void testBuiltInValueSetFetchAndExpand() {
@ -144,33 +188,6 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
assertThat(resp, not(containsString("<code value=\"8450-9\"/>"))); assertThat(resp, not(containsString("<code value=\"8450-9\"/>")));
} }
@Test
public void testValidateCodeOperationByCodeAndSystemBad() {
UriType valueSetIdentifier = null;
IdType id = null;
CodeType code = new CodeType("8450-9-XXX");
UriType system = new UriType("http://acme.org");
StringType display = null;
Coding coding = null;
CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertFalse(result.isResult());
}
@Test
public void testValidateCodeOperationByCodeAndSystemGood() {
UriType valueSetIdentifier = null;
IdType id = null;
CodeType code = new CodeType("8450-9");
UriType system = new UriType("http://acme.org");
StringType display = null;
Coding coding = null;
CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult());
assertEquals("Systolic blood pressure--expiration", result.getDisplay());
}
@Test @Test
public void testValidateCodeOperationByIdentifierAndCodeAndSystem() { public void testValidateCodeOperationByIdentifierAndCodeAndSystem() {
UriType valueSetIdentifier = new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"); UriType valueSetIdentifier = new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
@ -180,8 +197,8 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
StringType display = null; StringType display = null;
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -194,8 +211,8 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
StringType display = new StringType("Systolic blood pressure at First encounterXXXX"); StringType display = new StringType("Systolic blood pressure at First encounterXXXX");
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertFalse(result.isResult()); assertFalse(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -208,8 +225,8 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
StringType display = new StringType("Systolic blood pressure at First encounter"); StringType display = new StringType("Systolic blood pressure at First encounter");
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -222,8 +239,8 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
StringType display = null; StringType display = null;
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -237,8 +254,8 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = new CodeableConcept(); CodeableConcept codeableConcept = new CodeableConcept();
codeableConcept.addCoding().setSystem("http://acme.org").setCode("11378-7"); codeableConcept.addCoding().setSystem("http://acme.org").setCode("11378-7");
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -250,13 +267,12 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
StringType vsIdentifier = new StringType("http://hl7.org/fhir/ValueSet/administrative-gender"); StringType vsIdentifier = new StringType("http://hl7.org/fhir/ValueSet/administrative-gender");
StringType code = new StringType("male"); StringType code = new StringType("male");
StringType system = new StringType("http://hl7.org/fhir/administrative-gender"); StringType system = new StringType("http://hl7.org/fhir/administrative-gender");
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(vsIdentifier, null, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(vsIdentifier, null, code, system, display, coding, codeableConcept, mySrd);
ourLog.info(result.getMessage()); ourLog.info(result.getMessage());
assertTrue(result.isResult(), result.getMessage()); assertTrue(result.isOk(), result.getMessage());
} }
} }

View File

@ -447,8 +447,6 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
protected ITermConceptMapGroupElementTargetDao myTermConceptMapGroupElementTargetDao; protected ITermConceptMapGroupElementTargetDao myTermConceptMapGroupElementTargetDao;
@Autowired @Autowired
protected ICacheWarmingSvc myCacheWarmingSvc; protected ICacheWarmingSvc myCacheWarmingSvc;
@Autowired
protected SubscriptionRegistry mySubscriptionRegistry;
protected IServerInterceptor myInterceptor; protected IServerInterceptor myInterceptor;
@Autowired @Autowired
protected DaoRegistry myDaoRegistry; protected DaoRegistry myDaoRegistry;

View File

@ -163,7 +163,7 @@ public class BaseR4SearchLastN extends BaseJpaTest {
} }
private Date calculateObservationDateFromOffset(Integer theTimeOffset, Integer theObservationIndex) { private Date calculateObservationDateFromOffset(Integer theTimeOffset, Integer theObservationIndex) {
int milliSecondsPerHour = 3600 * 1000; long milliSecondsPerHour = 3600L * 1000L;
// Generate a Date by subtracting a calculated number of hours from the static observationDate property. // Generate a Date by subtracting a calculated number of hours from the static observationDate property.
return new Date(observationDate.getTimeInMillis() - (milliSecondsPerHour * (theTimeOffset + theObservationIndex))); return new Date(observationDate.getTimeInMillis() - (milliSecondsPerHour * (theTimeOffset + theObservationIndex)));
} }

View File

@ -5,6 +5,7 @@ import ca.uhn.fhir.jpa.api.model.TranslationRequest;
import ca.uhn.fhir.jpa.api.model.TranslationResult; import ca.uhn.fhir.jpa.api.model.TranslationResult;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.ConceptMap;
@ -1042,6 +1043,49 @@ public class FhirResourceDaoR4ConceptMapTest extends BaseJpaR4Test {
}); });
} }
/**
* Some US core ConceptMaps use this style, e.g:
*
* http://hl7.org/fhir/us/core/ConceptMap/ndc-cvx
*/
@Test
public void testUploadConceptMapWithOnlyCanonicalSourceAtConceptMapLevel() {
ConceptMap cm = new ConceptMap();
cm.setUrl("http://foo");
cm.setSource(new CanonicalType("http://source"));
cm.setTarget(new CanonicalType("http://target"));
cm.addGroup().addElement().setCode("source1").addTarget().setCode("target1").setEquivalence(ConceptMapEquivalence.EQUAL);
myConceptMapDao.create(cm);
runInTransaction(()->{
TranslationRequest translationRequest = new TranslationRequest();
translationRequest.getCodeableConcept().addCoding()
.setSystem("http://source")
.setCode("source1");
translationRequest.setTarget(new UriType("http://target"));
ourLog.info("*** About to translate");
TranslationResult translationResult = myConceptMapDao.translate(translationRequest, null);
ourLog.info("*** Done translating");
assertTrue(translationResult.getResult().booleanValue());
assertEquals("Matches found!", translationResult.getMessage().getValueAsString());
assertEquals(1, translationResult.getMatches().size());
TranslationMatch translationMatch = translationResult.getMatches().get(0);
assertEquals("equal", translationMatch.getEquivalence().getCode());
Coding concept = translationMatch.getConcept();
assertEquals("target1", concept.getCode());
assertEquals(null, concept.getDisplay());
assertEquals("http://target", concept.getSystem());
});
}
@Test @Test
public void testUploadAndApplyR4DemoConceptMap() throws IOException { public void testUploadAndApplyR4DemoConceptMap() throws IOException {

View File

@ -0,0 +1,442 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.interceptor.executor.InterceptorService;
import ca.uhn.fhir.jpa.interceptor.UserRequestRetryVersionConflictsInterceptor;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.PatchTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.HapiExtensions;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.SearchParameter;
import org.hl7.fhir.r4.model.StringType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@SuppressWarnings({"unchecked", "deprecation", "Duplicates"})
public class FhirResourceDaoR4ConcurrentWriteTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4ConcurrentWriteTest.class);
private ExecutorService myExecutor;
private UserRequestRetryVersionConflictsInterceptor myRetryInterceptor;
@BeforeEach
public void before() {
myExecutor = Executors.newFixedThreadPool(10);
myRetryInterceptor = new UserRequestRetryVersionConflictsInterceptor();
RestfulServer server = new RestfulServer(myFhirCtx);
when(mySrd.getServer()).thenReturn(server);
}
@AfterEach
public void after() {
myExecutor.shutdown();
myInterceptorRegistry.unregisterInterceptor(myRetryInterceptor);
}
@Test
public void testCreateWithClientAssignedId() {
myInterceptorRegistry.registerInterceptor(myRetryInterceptor);
String value = UserRequestRetryVersionConflictsInterceptor.RETRY + "; " + UserRequestRetryVersionConflictsInterceptor.MAX_RETRIES + "=10";
when(mySrd.getHeaders(eq(UserRequestRetryVersionConflictsInterceptor.HEADER_NAME))).thenReturn(Collections.singletonList(value));
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Patient p = new Patient();
p.setId("ABC");
p.setActive(true);
p.addIdentifier().setValue("VAL" + i);
Runnable task = () -> myPatientDao.update(p, mySrd);
Future<?> future = myExecutor.submit(task);
futures.add(future);
}
// Look for failures
for (Future<?> next : futures) {
try {
next.get();
ourLog.info("Future produced success");
} catch (Exception e) {
ourLog.info("Future produced exception: {}", e.toString());
throw new AssertionError("Failed with message: " + e.toString(), e);
}
}
// Make sure we saved the object
Patient patient = myPatientDao.read(new IdType("Patient/ABC"));
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient));
assertEquals(true, patient.getActive());
}
@Test
public void testCreateWithUniqueConstraint() {
SearchParameter sp = new SearchParameter();
sp.setId("SearchParameter/patient-gender");
sp.setType(Enumerations.SearchParamType.TOKEN);
sp.setCode("gender");
sp.setExpression("Patient.gender");
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
sp.addBase("Patient");
mySearchParameterDao.update(sp);
sp = new SearchParameter();
sp.setId("SearchParameter/patient-gender-unique");
sp.setType(Enumerations.SearchParamType.COMPOSITE);
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
sp.addBase("Patient");
sp.addComponent()
.setExpression("Patient")
.setDefinition("SearchParameter/patient-gender");
sp.addExtension()
.setUrl(HapiExtensions.EXT_SP_UNIQUE)
.setValue(new BooleanType(true));
mySearchParameterDao.update(sp);
mySearchParamRegistry.forceRefresh();
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Patient p = new Patient();
p.setGender(Enumerations.AdministrativeGender.MALE);
p.addIdentifier().setValue("VAL" + i);
Runnable task = () -> {
try {
myPatientDao.create(p);
} catch (PreconditionFailedException e) {
// expected - This is as a result of the unique SP
assertThat(e.getMessage(), containsString("duplicate index matching query: Patient?gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"));
} catch (ResourceVersionConflictException e) {
// expected - This is as a result of the unique SP
assertThat(e.getMessage(), containsString("would have resulted in a duplicate value for a unique index"));
}
};
Future<?> future = myExecutor.submit(task);
futures.add(future);
}
// Look for failures
for (Future<?> next : futures) {
try {
next.get();
ourLog.info("Future produced success");
} catch (Exception e) {
ourLog.info("Future produced exception: {}", e.toString());
throw new AssertionError("Failed with message: " + e.toString(), e);
}
}
runInTransaction(() -> {
ourLog.info("Uniques:\n * " + myResourceIndexedCompositeStringUniqueDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
});
// Make sure we saved the object
myCaptureQueriesListener.clear();
IBundleProvider search = myPatientDao.search(SearchParameterMap.newSynchronous("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")));
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(1, search.sizeOrThrowNpe());
}
@Test
public void testDelete() {
myInterceptorRegistry.registerInterceptor(myRetryInterceptor);
String value = UserRequestRetryVersionConflictsInterceptor.RETRY + "; " + UserRequestRetryVersionConflictsInterceptor.MAX_RETRIES + "=100";
when(mySrd.getHeaders(eq(UserRequestRetryVersionConflictsInterceptor.HEADER_NAME))).thenReturn(Collections.singletonList(value));
IIdType patientId = runInTransaction(() -> {
Patient p = new Patient();
p.setActive(true);
return myPatientDao.create(p).getId().toUnqualifiedVersionless();
});
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 5; i++) {
// Submit an update
Patient p = new Patient();
p.setId(patientId);
p.addIdentifier().setValue("VAL" + i);
Runnable task = () -> myPatientDao.update(p, mySrd);
Future<?> future = myExecutor.submit(task);
futures.add(future);
// Submit a delete
task = () -> myPatientDao.delete(patientId, mySrd);
future = myExecutor.submit(task);
futures.add(future);
}
// Look for failures
for (Future<?> next : futures) {
try {
next.get();
ourLog.info("Future produced success");
} catch (Exception e) {
ourLog.info("Future produced exception: {}", e.toString());
throw new AssertionError("Failed with message: " + e.toString(), e);
}
}
// Make sure we saved the object
IBundleProvider patient = myPatientDao.history(patientId, null, null, null);
assertThat(patient.sizeOrThrowNpe(), greaterThanOrEqualTo(3));
}
@Test
public void testNoRetryRequest() {
myInterceptorRegistry.registerInterceptor(myRetryInterceptor);
when(mySrd.getHeaders(eq(UserRequestRetryVersionConflictsInterceptor.HEADER_NAME))).thenReturn(Collections.emptyList());
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Patient p = new Patient();
p.setId("ABC");
p.setActive(true);
p.addIdentifier().setValue("VAL" + i);
Runnable task = () -> myPatientDao.update(p, mySrd);
Future<?> future = myExecutor.submit(task);
futures.add(future);
}
// Look for failures
for (Future<?> next : futures) {
try {
next.get();
ourLog.info("Future produced success");
} catch (ExecutionException | InterruptedException e) {
if (e.getCause() instanceof ResourceVersionConflictException) {
// this is expected since we're not retrying
ourLog.info("Version conflict (expected): {}", e.getCause().toString());
} else {
ourLog.info("Future produced exception: {}", e.toString());
throw new AssertionError("Failed with message: " + e.toString(), e);
}
}
}
// Make sure we saved the object
Patient patient = myPatientDao.read(new IdType("Patient/ABC"));
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient));
assertEquals(true, patient.getActive());
}
@Test
public void testNoRetryInterceptor() {
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Patient p = new Patient();
p.setId("ABC");
p.setActive(true);
p.addIdentifier().setValue("VAL" + i);
Runnable task = () -> myPatientDao.update(p, mySrd);
Future<?> future = myExecutor.submit(task);
futures.add(future);
}
// Look for failures
for (Future<?> next : futures) {
try {
next.get();
ourLog.info("Future produced success");
} catch (ExecutionException | InterruptedException e) {
if (e.getCause() instanceof ResourceVersionConflictException) {
// this is expected since we're not retrying
ourLog.info("Version conflict (expected): {}", e.getCause().toString());
} else {
ourLog.info("Future produced exception: {}", e.toString());
throw new AssertionError("Failed with message: " + e.toString(), e);
}
}
}
// Make sure we saved the object
Patient patient = myPatientDao.read(new IdType("Patient/ABC"));
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient));
assertEquals(true, patient.getActive());
}
@Test
public void testNoRequestDetails() {
myInterceptorRegistry.registerInterceptor(myRetryInterceptor);
when(mySrd.getHeaders(eq(UserRequestRetryVersionConflictsInterceptor.HEADER_NAME))).thenReturn(Collections.emptyList());
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Patient p = new Patient();
p.setId("ABC");
p.setActive(true);
p.addIdentifier().setValue("VAL" + i);
Runnable task = () -> myPatientDao.update(p);
Future<?> future = myExecutor.submit(task);
futures.add(future);
}
// Look for failures
for (Future<?> next : futures) {
try {
next.get();
ourLog.info("Future produced success");
} catch (ExecutionException | InterruptedException e) {
if (e.getCause() instanceof ResourceVersionConflictException) {
// this is expected since we're not retrying
ourLog.info("Version conflict (expected): {}", e.getCause().toString());
} else {
ourLog.info("Future produced exception: {}", e.toString());
throw new AssertionError("Failed with message: " + e.toString(), e);
}
}
}
// Make sure we saved the object
Patient patient = myPatientDao.read(new IdType("Patient/ABC"));
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient));
assertEquals(true, patient.getActive());
}
@Test
public void testPatch() {
myInterceptorRegistry.registerInterceptor(myRetryInterceptor);
String value = UserRequestRetryVersionConflictsInterceptor.RETRY + "; " + UserRequestRetryVersionConflictsInterceptor.MAX_RETRIES + "=10";
when(mySrd.getHeaders(eq(UserRequestRetryVersionConflictsInterceptor.HEADER_NAME))).thenReturn(Collections.singletonList(value));
Patient p = new Patient();
p.addName().setFamily("FAMILY");
IIdType pId = myPatientDao.create(p).getId().toUnqualifiedVersionless();
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Parameters patch = new Parameters();
Parameters.ParametersParameterComponent operation = patch.addParameter();
operation.setName("operation");
operation
.addPart()
.setName("type")
.setValue(new CodeType("replace"));
operation
.addPart()
.setName("path")
.setValue(new StringType("Patient.name[0].family"));
operation
.addPart()
.setName("value")
.setValue(new StringType("FAMILY-" + i));
Runnable task = () -> myPatientDao.patch(pId, null, PatchTypeEnum.FHIR_PATCH_JSON, null, patch, mySrd);
Future<?> future = myExecutor.submit(task);
futures.add(future);
}
// Look for failures
for (Future<?> next : futures) {
try {
next.get();
ourLog.info("Future produced success");
} catch (Exception e) {
ourLog.info("Future produced exception: {}", e.toString());
throw new AssertionError("Failed with message: " + e.toString(), e);
}
}
// Make sure we saved the object
Patient patient = myPatientDao.read(pId);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient));
assertEquals("6", patient.getMeta().getVersionId());
}
@Test
public void testTransactionWithCreate() {
myInterceptorRegistry.registerInterceptor(myRetryInterceptor);
ServletRequestDetails srd = mock(ServletRequestDetails.class);
String value = UserRequestRetryVersionConflictsInterceptor.RETRY + "; " + UserRequestRetryVersionConflictsInterceptor.MAX_RETRIES + "=10";
when(srd.getHeaders(eq(UserRequestRetryVersionConflictsInterceptor.HEADER_NAME))).thenReturn(Collections.singletonList(value));
when(srd.getUserData()).thenReturn(new HashMap<>());
when(srd.getServer()).thenReturn(new RestfulServer(myFhirCtx));
when(srd.getInterceptorBroadcaster()).thenReturn(new InterceptorService());
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Patient p = new Patient();
p.setId("ABC");
p.setActive(true);
p.addIdentifier().setValue("VAL" + i);
Bundle bundle = new Bundle();
bundle.setType(Bundle.BundleType.TRANSACTION);
bundle
.addEntry()
.setResource(p)
.getRequest()
.setMethod(Bundle.HTTPVerb.PUT)
.setUrl("Patient/ABC");
Runnable task = () -> mySystemDao.transaction(srd, bundle);
Future<?> future = myExecutor.submit(task);
futures.add(future);
}
// Look for failures
for (Future<?> next : futures) {
try {
next.get();
ourLog.info("Future produced success");
} catch (Exception e) {
ourLog.info("Future produced exception: {}", e.toString());
throw new AssertionError("Failed with message: " + e.toString(), e);
}
}
// Make sure we saved the object
Patient patient = myPatientDao.read(new IdType("Patient/ABC"));
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient));
assertEquals(true, patient.getActive());
}
}

View File

@ -441,7 +441,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
Observation obs = new Observation(); Observation obs = new Observation();
obs.getSubject().setReference("Patient/P"); obs.getSubject().setReference("Patient/P");
myObservationDao.update(obs); myObservationDao.create(obs);
SearchParameterMap map = new SearchParameterMap(); SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true); map.setLoadSynchronous(true);
@ -482,7 +482,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
Observation obs = new Observation(); Observation obs = new Observation();
obs.getSubject().setReference("Patient/P"); obs.getSubject().setReference("Patient/P");
myObservationDao.update(obs); myObservationDao.create(obs);
SearchParameterMap map = new SearchParameterMap(); SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true); map.setLoadSynchronous(true);

View File

@ -697,7 +697,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
myCaptureQueriesListener.logSelectQueries(); myCaptureQueriesListener.logSelectQueries();
String selectQuery = myCaptureQueriesListener.getSelectQueries().get(1).getSql(true, true); String selectQuery = myCaptureQueriesListener.getSelectQueries().get(1).getSql(true, true);
assertThat(selectQuery, containsString("HASH_VALUE=")); assertThat(selectQuery, containsString("HASH_VALUE"));
assertThat(selectQuery, not(containsString("HASH_SYS"))); assertThat(selectQuery, not(containsString("HASH_SYS")));
} }

View File

@ -398,7 +398,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(unformattedSql, stringContainsInOrder( assertThat(unformattedSql, stringContainsInOrder(
"IDX_STRING='Patient?identifier=urn%7C111'", "IDX_STRING='Patient?identifier=urn%7C111'",
"HASH_SYS_AND_VALUE='-3122824860083758210'" "HASH_SYS_AND_VALUE in ('-3122824860083758210')"
)); ));
assertThat(unformattedSql, not(containsString(("RES_DELETED_AT")))); assertThat(unformattedSql, not(containsString(("RES_DELETED_AT"))));
assertThat(unformattedSql, not(containsString(("RES_TYPE")))); assertThat(unformattedSql, not(containsString(("RES_TYPE"))));
@ -535,7 +535,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(unformattedSql, stringContainsInOrder( assertThat(unformattedSql, stringContainsInOrder(
"IDX_STRING='ServiceRequest?identifier=sys%7C111&patient=Patient%2F" + ptId.getIdPart() + "&performer=Practitioner%2F" + practId.getIdPart() + "'", "IDX_STRING='ServiceRequest?identifier=sys%7C111&patient=Patient%2F" + ptId.getIdPart() + "&performer=Practitioner%2F" + practId.getIdPart() + "'",
"HASH_SYS_AND_VALUE='6795110643554413877'" "HASH_SYS_AND_VALUE in ('6795110643554413877')"
)); ));
assertThat(unformattedSql, not(containsString(("RES_DELETED_AT")))); assertThat(unformattedSql, not(containsString(("RES_DELETED_AT"))));
assertThat(unformattedSql, not(containsString(("RES_TYPE")))); assertThat(unformattedSql, not(containsString(("RES_TYPE"))));
@ -556,8 +556,8 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
assertThat(toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder(srId)); assertThat(toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder(srId));
unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(unformattedSql, stringContainsInOrder( assertThat(unformattedSql, stringContainsInOrder(
"SRC_PATH='ServiceRequest.subject.where(resolve() is Patient)'", "SRC_PATH in ('ServiceRequest.subject.where(resolve() is Patient)')",
"SRC_PATH='ServiceRequest.performer'" "SRC_PATH in ('ServiceRequest.performer')"
)); ));
assertThat(unformattedSql, not(containsString(("RES_DELETED_AT")))); assertThat(unformattedSql, not(containsString(("RES_DELETED_AT"))));
assertThat(unformattedSql, not(containsString(("RES_TYPE")))); assertThat(unformattedSql, not(containsString(("RES_TYPE"))));

View File

@ -9,7 +9,10 @@ import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum; import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl; import ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl;
import ca.uhn.fhir.jpa.term.TerminologyLoaderSvcLoincTest;
import ca.uhn.fhir.jpa.term.ZipCollectionBuilder;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet; import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
@ -36,9 +39,11 @@ import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Condition; import org.hl7.fhir.r4.model.Condition;
import org.hl7.fhir.r4.model.DateTimeType; import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.ElementDefinition;
import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.Group; import org.hl7.fhir.r4.model.Group;
import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.IdType;
@ -49,11 +54,13 @@ import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Organization; import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Practitioner; import org.hl7.fhir.r4.model.Practitioner;
import org.hl7.fhir.r4.model.Quantity;
import org.hl7.fhir.r4.model.Questionnaire; import org.hl7.fhir.r4.model.Questionnaire;
import org.hl7.fhir.r4.model.QuestionnaireResponse; import org.hl7.fhir.r4.model.QuestionnaireResponse;
import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.StructureDefinition; import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.UriType;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r5.utils.IResourceValidator; import org.hl7.fhir.r5.utils.IResourceValidator;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -73,6 +80,7 @@ import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@ -95,6 +103,105 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
@Autowired @Autowired
private ValidationSettings myValidationSettings; private ValidationSettings myValidationSettings;
@Test
public void testValidateCodeInValueSetWithUnknownCodeSystem() {
myValidationSupport.fetchCodeSystem("http://not-exist"); // preload DefaultProfileValidationSupport
ValueSet vs = new ValueSet();
vs.setUrl("http://vs");
vs
.getCompose()
.addInclude()
.setSystem("http://cs")
.addConcept(new ValueSet.ConceptReferenceComponent(new CodeType("code1")))
.addConcept(new ValueSet.ConceptReferenceComponent(new CodeType("code2")));
myValueSetDao.create(vs);
StructureDefinition sd = new StructureDefinition();
sd.setDerivation(StructureDefinition.TypeDerivationRule.CONSTRAINT);
sd.setType("Observation");
sd.setUrl("http://sd");
sd.setBaseDefinition("http://hl7.org/fhir/StructureDefinition/Observation");
sd.getDifferential()
.addElement()
.setPath("Observation.value[x]")
.addType(new ElementDefinition.TypeRefComponent(new UriType("Quantity")))
.setBinding(new ElementDefinition.ElementDefinitionBindingComponent().setStrength(Enumerations.BindingStrength.REQUIRED).setValueSet("http://vs"))
.setId("Observation.value[x]");
myStructureDefinitionDao.create(sd);
Observation obs = new Observation();
obs.getMeta().addProfile("http://sd");
obs.getText().setDivAsString("<div>Hello</div>");
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.getCode().setText("hello");
obs.setSubject(new Reference("Patient/123"));
obs.addPerformer(new Reference("Practitioner/123"));
obs.setEffective(DateTimeType.now());
obs.setStatus(ObservationStatus.FINAL);
OperationOutcome oo;
// Valid code
obs.setValue(new Quantity().setSystem("http://cs").setCode("code1").setValue(123));
oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals("No issues detected during validation", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
// Invalid code
obs.setValue(new Quantity().setSystem("http://cs").setCode("code99").setValue(123));
oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals("Could not confirm that the codes provided are in the value set http://vs, and a code from this value set is required", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
}
@Test
public void testGenerateSnapshotOnStructureDefinitionWithNoBase() {
// No base populated here, which isn't valid
StructureDefinition sd = new StructureDefinition();
sd.setDerivation(StructureDefinition.TypeDerivationRule.CONSTRAINT);
sd.setUrl("http://sd");
sd.getDifferential()
.addElement()
.setPath("Observation.value[x]")
.addType(new ElementDefinition.TypeRefComponent(new UriType("string")))
.setId("Observation.value[x]");
try {
myStructureDefinitionDao.generateSnapshot(sd, null, null, null);
fail();
} catch (PreconditionFailedException e) {
assertEquals("StructureDefinition[id=null, url=http://sd] has no base", e.getMessage());
}
myStructureDefinitionDao.create(sd);
Observation obs = new Observation();
obs.getMeta().addProfile("http://sd");
obs.getText().setDivAsString("<div>Hello</div>");
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.getCode().setText("hello");
obs.setSubject(new Reference("Patient/123"));
obs.addPerformer(new Reference("Practitioner/123"));
obs.setEffective(DateTimeType.now());
obs.setStatus(ObservationStatus.FINAL);
OperationOutcome oo;
// Valid code
obs.setValue(new Quantity().setSystem("http://cs").setCode("code1").setValue(123));
try {
myObservationDao.validate(obs, null, null, null, ValidationModeEnum.CREATE, null, mySrd);
fail();
} catch (PreconditionFailedException e) {
assertEquals("StructureDefinition[id=null, url=http://sd] has no base", e.getMessage());
}
}
/** /**
* Use a valueset that explicitly brings in some UCUM codes * Use a valueset that explicitly brings in some UCUM codes
*/ */
@ -505,6 +612,64 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
} }
@Test
public void testValidateValueSet() {
String input = "{\n" +
" \"resourceType\": \"ValueSet\",\n" +
" \"meta\": {\n" +
" \"profile\": [\n" +
" \"https://foo\"\n" +
" ]\n" +
" },\n" +
" \"text\": {\n" +
" \"status\": \"generated\",\n" +
" \"div\": \"<div xmlns=\\\"http://www.w3.org/1999/xhtml\\\"></div>\"\n" +
" },\n" +
" \"url\": \"https://foo/bb\",\n" +
" \"name\": \"BBBehaviourType\",\n" +
" \"title\": \"BBBehaviour\",\n" +
" \"status\": \"draft\",\n" +
" \"version\": \"20190731\",\n" +
" \"experimental\": false,\n" +
" \"description\": \"alcohol habits.\",\n" +
" \"publisher\": \"BB\",\n" +
" \"immutable\": false,\n" +
" \"compose\": {\n" +
" \"include\": [\n" +
" {\n" +
" \"system\": \"https://bb\",\n" +
" \"concept\": [\n" +
" {\n" +
" \"code\": \"123\",\n" +
" \"display\": \"Current drinker\"\n" +
" },\n" +
" {\n" +
" \"code\": \"456\",\n" +
" \"display\": \"Ex-drinker\"\n" +
" },\n" +
" {\n" +
" \"code\": \"789\",\n" +
" \"display\": \"Lifetime non-drinker (finding)\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" ]\n" +
" }\n" +
"}";
ValueSet vs = myFhirCtx.newJsonParser().parseResource(ValueSet.class, input);
OperationOutcome oo = validateAndReturnOutcome(vs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals("The code 123 is not valid in the system https://bb", oo.getIssue().get(0).getDiagnostics());
}
/** /**
* Per: https://chat.fhir.org/#narrow/stream/179166-implementers/topic/Handling.20incomplete.20CodeSystems * Per: https://chat.fhir.org/#narrow/stream/179166-implementers/topic/Handling.20incomplete.20CodeSystems
* <p> * <p>
@ -533,16 +698,10 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
obs.getCode().getCodingFirstRep().setSystem("http://example.com/codesystem"); obs.getCode().getCodingFirstRep().setSystem("http://example.com/codesystem");
obs.getCode().getCodingFirstRep().setCode("foo-foo"); obs.getCode().getCodingFirstRep().setCode("foo-foo");
obs.getCode().getCodingFirstRep().setDisplay("Some Code"); obs.getCode().getCodingFirstRep().setDisplay("Some Code");
try { outcome = (OperationOutcome) myObservationDao.validate(obs, null, null, null, ValidationModeEnum.CREATE, "http://example.com/structuredefinition", mySrd).getOperationOutcome();
outcome = (OperationOutcome) myObservationDao.validate(obs, null, null, null, ValidationModeEnum.CREATE, "http://example.com/structuredefinition", mySrd).getOperationOutcome(); ourLog.info("Outcome: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
ourLog.info("Outcome: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); assertEquals("Unknown code in fragment CodeSystem 'http://example.com/codesystem#foo-foo'", outcome.getIssueFirstRep().getDiagnostics());
fail(); assertEquals(OperationOutcome.IssueSeverity.WARNING, outcome.getIssueFirstRep().getSeverity());
} catch (PreconditionFailedException e) {
OperationOutcome oo = (OperationOutcome) e.getOperationOutcome();
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals("None of the codes provided are in the value set http://example.com/valueset (http://example.com/valueset), and a code from this value set is required) (codes = http://example.com/codesystem#foo-foo)", oo.getIssueFirstRep().getDiagnostics());
assertEquals(OperationOutcome.IssueSeverity.ERROR, oo.getIssueFirstRep().getSeverity());
}
// Correct codesystem, Code in codesystem // Correct codesystem, Code in codesystem
obs.getCode().getCodingFirstRep().setSystem("http://example.com/codesystem"); obs.getCode().getCodingFirstRep().setSystem("http://example.com/codesystem");
@ -1442,4 +1601,20 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
} }
@Test
public void testKnownCodeSystemUnknownValueSetUri() {
CodeSystem cs = new CodeSystem();
cs.setUrl(ITermLoaderSvc.LOINC_URI);
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
cs.addConcept().setCode("10013-1");
myCodeSystemDao.create(cs);
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(new UriType("http://fooVs"), null, new StringType("10013-1"), new StringType(ITermLoaderSvc.LOINC_URI), null, null, null, mySrd);
assertFalse(result.isOk());
assertEquals("Unknown code {http://loinc.org}10013-1 - Unable to locate ValueSet[http://fooVs]", result.getMessage());
}
} }

View File

@ -1,10 +1,10 @@
package ca.uhn.fhir.jpa.dao.r4; package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet; import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
@ -15,7 +15,6 @@ import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType; import org.hl7.fhir.r4.model.UriType;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -49,14 +48,14 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
public void before02() throws IOException { public void before02() throws IOException {
ValueSet upload = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); ValueSet upload = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml");
myExtensionalVsId = myValueSetDao.create(upload, mySrd).getId().toUnqualifiedVersionless(); myExtensionalVsId = myValueSetDao.create(upload, mySrd).getId().toUnqualifiedVersionless();
CodeSystem upload2 = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs.xml"); CodeSystem upload2 = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs.xml");
myCodeSystemDao.create(upload2, mySrd).getId().toUnqualifiedVersionless(); myCodeSystemDao.create(upload2, mySrd).getId().toUnqualifiedVersionless();
} }
@Test @Test
public void testValidateCodeOperationByCodeAndSystemBad() { public void testValidateCodeOperationNoValueSet() {
UriType valueSetIdentifier = null; UriType valueSetIdentifier = null;
IdType id = null; IdType id = null;
CodeType code = new CodeType("8450-9-XXX"); CodeType code = new CodeType("8450-9-XXX");
@ -64,22 +63,12 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
StringType display = null; StringType display = null;
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); try {
assertFalse(result.isResult()); myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
} fail();
} catch (InvalidRequestException e) {
@Test assertEquals("Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate.", e.getMessage());
public void testValidateCodeOperationByCodeAndSystemGood() { }
UriType valueSetIdentifier = null;
IdType id = null;
CodeType code = new CodeType("8450-9");
UriType system = new UriType("http://acme.org");
StringType display = null;
Coding coding = null;
CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult());
assertEquals("Systolic blood pressure--expiration", result.getDisplay());
} }
@Test @Test
@ -91,8 +80,8 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
StringType display = null; StringType display = null;
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -105,9 +94,10 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
StringType display = new StringType("Systolic blood pressure at First encounterXXXX"); StringType display = new StringType("Systolic blood pressure at First encounterXXXX");
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertFalse(result.isResult()); assertFalse(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
assertEquals("Concept Display \"Systolic blood pressure at First encounterXXXX\" does not match expected \"Systolic blood pressure at First encounter\"", result.getMessage());
} }
@Test @Test
@ -119,8 +109,8 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
StringType display = new StringType("Systolic blood pressure at First encounter"); StringType display = new StringType("Systolic blood pressure at First encounter");
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -134,8 +124,8 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = new CodeableConcept(); CodeableConcept codeableConcept = new CodeableConcept();
codeableConcept.addCoding().setSystem("http://acme.org").setCode("11378-7"); codeableConcept.addCoding().setSystem("http://acme.org").setCode("11378-7");
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -151,18 +141,18 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = new CodeableConcept(); CodeableConcept codeableConcept = new CodeableConcept();
codeableConcept.addCoding().setSystem("http://acme.org").setCode("11378-7"); codeableConcept.addCoding().setSystem("http://acme.org").setCode("11378-7");
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
myTerminologyDeferredStorageSvc.saveDeferred(); myTerminologyDeferredStorageSvc.saveDeferred();
result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -175,8 +165,8 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
StringType display = null; StringType display = null;
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -191,18 +181,18 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
StringType display = null; StringType display = null;
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
myTerminologyDeferredStorageSvc.saveDeferred(); myTerminologyDeferredStorageSvc.saveDeferred();
result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -213,17 +203,17 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
ValueSet expanded = myValueSetDao.expand(myExtensionalVsId, null, mySrd); ValueSet expanded = myValueSetDao.expand(myExtensionalVsId, null, mySrd);
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp); ourLog.info(resp);
assertThat(resp, containsString("<ValueSet xmlns=\"http://hl7.org/fhir\">")); assertThat(resp, containsString("<ValueSet xmlns=\"http://hl7.org/fhir\">"));
assertThat(resp, containsString("<expansion>")); assertThat(resp, containsString("<expansion>"));
assertThat(resp, containsString("<contains>")); assertThat(resp, containsString("<contains>"));
assertThat(resp, containsString("<system value=\"http://acme.org\"/>")); assertThat(resp, containsString("<system value=\"http://acme.org\"/>"));
assertThat(resp, containsString("<code value=\"8450-9\"/>")); assertThat(resp, containsString("<code value=\"8450-9\"/>"));
assertThat(resp, containsString("<display value=\"Systolic blood pressure--expiration\"/>")); assertThat(resp, containsString("<display value=\"Systolic blood pressure--expiration\"/>"));
assertThat(resp, containsString("</contains>")); assertThat(resp, containsString("</contains>"));
assertThat(resp, containsString("<contains>")); assertThat(resp, containsString("<contains>"));
assertThat(resp, containsString("<system value=\"http://acme.org\"/>")); assertThat(resp, containsString("<system value=\"http://acme.org\"/>"));
assertThat(resp, containsString("<code value=\"11378-7\"/>")); assertThat(resp, containsString("<code value=\"11378-7\"/>"));
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter\"/>")); assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter\"/>"));
assertThat(resp, containsString("</contains>")); assertThat(resp, containsString("</contains>"));
assertThat(resp, containsString("</expansion>")); assertThat(resp, containsString("</expansion>"));
@ -236,12 +226,12 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
ourLog.info(resp); ourLog.info(resp);
//@formatter:off //@formatter:off
assertThat(resp, stringContainsInOrder( assertThat(resp, stringContainsInOrder(
"<code value=\"11378-7\"/>", "<code value=\"11378-7\"/>",
"<display value=\"Systolic blood pressure at First encounter\"/>")); "<display value=\"Systolic blood pressure at First encounter\"/>"));
//@formatter:on //@formatter:on
} }
@Test @Test
public void testExpandByValueSet_ExceedsMaxSize() { public void testExpandByValueSet_ExceedsMaxSize() {
// Add a bunch of codes // Add a bunch of codes
@ -265,7 +255,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
} }
} }
@Test @Test
public void testValidateCodeAgainstBuiltInValueSetAndCodeSystemWithValidCode() { public void testValidateCodeAgainstBuiltInValueSetAndCodeSystemWithValidCode() {
IPrimitiveType<String> display = null; IPrimitiveType<String> display = null;
@ -274,14 +264,14 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
StringType vsIdentifier = new StringType("http://hl7.org/fhir/ValueSet/administrative-gender"); StringType vsIdentifier = new StringType("http://hl7.org/fhir/ValueSet/administrative-gender");
StringType code = new StringType("male"); StringType code = new StringType("male");
StringType system = new StringType("http://hl7.org/fhir/administrative-gender"); StringType system = new StringType("http://hl7.org/fhir/administrative-gender");
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(vsIdentifier, null, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(vsIdentifier, null, code, system, display, coding, codeableConcept, mySrd);
ourLog.info(result.getMessage()); ourLog.info(result.getMessage());
assertTrue( result.isResult(), result.getMessage()); assertTrue(result.isOk(), result.getMessage());
assertEquals("Male", result.getDisplay()); assertEquals("Male", result.getDisplay());
} }
} }

View File

@ -1704,15 +1704,14 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
@Test @Test
public void testSearch_TagNotParam_SearchAllPartitions() { public void testSearch_TagNotParam_SearchAllPartitions() {
IIdType patientIdNull = createPatient(withPartition(null), withActiveTrue(), withTag("http://system", "code")); IIdType patientIdNull = createPatient(withPartition(null), withActiveTrue(), withTag("http://system", "code"), withIdentifier("http://foo", "bar"));
IIdType patientId1 = createPatient(withPartition(1), withActiveTrue(), withTag("http://system", "code")); IIdType patientId1 = createPatient(withPartition(1), withActiveTrue(), withTag("http://system", "code"), withIdentifier("http://foo", "bar"));
IIdType patientId2 = createPatient(withPartition(2), withActiveTrue(), withTag("http://system", "code")); IIdType patientId2 = createPatient(withPartition(2), withActiveTrue(), withTag("http://system", "code"));
createPatient(withPartition(null), withActiveTrue(), withTag("http://system", "code2")); createPatient(withPartition(null), withActiveTrue(), withTag("http://system", "code2"));
createPatient(withPartition(1), withActiveTrue(), withTag("http://system", "code2")); createPatient(withPartition(1), withActiveTrue(), withTag("http://system", "code2"));
createPatient(withPartition(2), withActiveTrue(), withTag("http://system", "code2")); createPatient(withPartition(2), withActiveTrue(), withTag("http://system", "code2"));
addReadAllPartitions(); addReadAllPartitions();
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap(); SearchParameterMap map = new SearchParameterMap();
map.add(Constants.PARAM_TAG, new TokenParam("http://system", "code2").setModifier(TokenParamModifier.NOT)); map.add(Constants.PARAM_TAG, new TokenParam("http://system", "code2").setModifier(TokenParamModifier.NOT));
@ -1725,6 +1724,26 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
ourLog.info("Search SQL:\n{}", searchSql); ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID")); assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM='http://system'")); assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM='http://system'"));
// And with another param
addReadAllPartitions();
myCaptureQueriesListener.clear();
map = new SearchParameterMap();
map.add(Constants.PARAM_TAG, new TokenParam("http://system", "code2").setModifier(TokenParamModifier.NOT));
map.add(Patient.SP_IDENTIFIER, new TokenParam("http://foo", "bar"));
map.setLoadSynchronous(true);
results = myPatientDao.search(map);
ids = toUnqualifiedVersionlessIds(results);
assertThat(ids, Matchers.contains(patientIdNull, patientId1));
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
assertEquals(1, StringUtils.countMatches(searchSql, "TAG_SYSTEM='http://system'"));
assertEquals(1, StringUtils.countMatches(searchSql, "myparamsto1_.HASH_SYS_AND_VALUE in"));
} }
@Test @Test
@ -1947,10 +1966,10 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
myCaptureQueriesListener.logSelectQueriesForCurrentThread(); myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, Matchers.contains(observationId)); assertThat(ids, Matchers.contains(observationId));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
ourLog.info("Search SQL:\n{}", searchSql); ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.PARTITION_ID='1'")); assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.PARTITION_ID='1'"));
assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.SRC_PATH='Observation.subject'")); assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.SRC_PATH in ('Observation.subject')"));
assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.TARGET_RESOURCE_ID='" + patientId.getIdPartAsLong() + "'")); assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.TARGET_RESOURCE_ID='" + patientId.getIdPartAsLong() + "'"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
@ -1985,10 +2004,10 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
myCaptureQueriesListener.logSelectQueriesForCurrentThread(); myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, Matchers.contains(observationId)); assertThat(ids, Matchers.contains(observationId));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
ourLog.info("Search SQL:\n{}", searchSql); ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.PARTITION_ID is null")); assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.PARTITION_ID is null"));
assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.SRC_PATH='Observation.subject'")); assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.SRC_PATH in ('Observation.subject')"));
assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.TARGET_RESOURCE_ID='" + patientId.getIdPartAsLong() + "'")); assertEquals(1, StringUtils.countMatches(searchSql, "myresource1_.TARGET_RESOURCE_ID='" + patientId.getIdPartAsLong() + "'"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID")); assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));

View File

@ -68,7 +68,7 @@ public class SearchWithInterceptorR4Test extends BaseJpaR4Test {
String query = list.get(0).getSql(true, false); String query = list.get(0).getSql(true, false);
ourLog.info("Query: {}", query); ourLog.info("Query: {}", query);
assertThat(query, containsString("HASH_SYS_AND_VALUE='3788488238034018567'")); assertThat(query, containsString("HASH_SYS_AND_VALUE in ('3788488238034018567')"));
} finally { } finally {
myInterceptorRegistry.unregisterInterceptor(interceptor); myInterceptorRegistry.unregisterInterceptor(interceptor);

View File

@ -9,6 +9,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Appointment; import org.hl7.fhir.r4.model.Appointment;
import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.utilities.graphql.Argument; import org.hl7.fhir.utilities.graphql.Argument;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices; import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.hl7.fhir.utilities.graphql.StringValue; import org.hl7.fhir.utilities.graphql.StringValue;
@ -22,6 +23,7 @@ import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -94,4 +96,29 @@ public class JpaStorageServicesTest extends BaseJpaR4Test {
assertEquals("Unknown GraphQL argument \"test\". Value GraphQL argument for this type are: [_id, _language, actor, appointment_type, based_on, date, identifier, location, part_status, patient, practitioner, reason_code, reason_reference, service_category, service_type, slot, specialty, status, supporting_info]", e.getMessage()); assertEquals("Unknown GraphQL argument \"test\". Value GraphQL argument for this type are: [_id, _language, actor, appointment_type, based_on, date, identifier, location, part_status, patient, practitioner, reason_code, reason_reference, service_category, service_type, slot, specialty, status, supporting_info]", e.getMessage());
} }
} }
private void createSomePatientWithId(String id) {
Patient somePatient = new Patient();
somePatient.setId(id);
myPatientDao.update(somePatient);
}
@Test
public void testListResourceGraphqlArrayOfArgument() {
createSomePatientWithId("hapi-123");
createSomePatientWithId("hapi-124");
Argument argument = new Argument();
argument.setName("_id");
argument.addValue(new StringValue("hapi-123"));
argument.addValue(new StringValue("hapi-124"));
List<IBaseResource> result = new ArrayList<>();
mySvc.listResources(mySrd, "Patient", Collections.singletonList(argument), result);
assertFalse(result.isEmpty());
List<String> expectedId = Arrays.asList("hapi-123", "hapi-124");
assertTrue(result.stream().allMatch((it) -> expectedId.contains(it.getIdElement().getIdPart())));
}
} }

View File

@ -98,12 +98,12 @@ public class IgInstallerDstu3Test extends BaseJpaDstu3Test {
PackageInstallationSpec spec = new PackageInstallationSpec() PackageInstallationSpec spec = new PackageInstallationSpec()
.setName("nictiz.fhir.nl.stu3.questionnaires") .setName("nictiz.fhir.nl.stu3.questionnaires")
.setVersion("1.0.2") .setVersion("1.0.2")
.setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL) .setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY)
.setFetchDependencies(true) .setFetchDependencies(true)
.addDependencyExclude("hl7\\.fhir\\.[a-zA-Z0-9]+\\.core"); .addDependencyExclude("hl7\\.fhir\\.[a-zA-Z0-9]+\\.core");
PackageInstallOutcomeJson outcome = igInstaller.install(spec); PackageInstallOutcomeJson outcome = igInstaller.install(spec);
ourLog.info("Install messages:\n * {}", outcome.getMessage().stream().collect(Collectors.joining("\n * "))); ourLog.info("Install messages:\n * {}", outcome.getMessage().stream().collect(Collectors.joining("\n * ")));
assertThat(outcome.getMessage(), hasItem("Indexing Resource[package/vl-QuestionnaireProvisioningTask.json] with URL: http://nictiz.nl/fhir/StructureDefinition/vl-QuestionnaireProvisioningTask|1.0.1")); assertThat(outcome.getMessage(), hasItem("Indexing StructureDefinition Resource[package/vl-QuestionnaireProvisioningTask.json] with URL: http://nictiz.nl/fhir/StructureDefinition/vl-QuestionnaireProvisioningTask|1.0.1"));
runInTransaction(() -> { runInTransaction(() -> {
assertTrue(myPackageVersionDao.findByPackageIdAndVersion("nictiz.fhir.nl.stu3.questionnaires", "1.0.2").isPresent()); assertTrue(myPackageVersionDao.findByPackageIdAndVersion("nictiz.fhir.nl.stu3.questionnaires", "1.0.2").isPresent());
@ -207,6 +207,6 @@ public class IgInstallerDstu3Test extends BaseJpaDstu3Test {
byte[] bytes = loadResourceAsByteArray("/packages/basisprofil.de.tar.gz"); byte[] bytes = loadResourceAsByteArray("/packages/basisprofil.de.tar.gz");
myFakeNpmServlet.getResponses().put("/basisprofil.de/0.2.40", bytes); myFakeNpmServlet.getResponses().put("/basisprofil.de/0.2.40", bytes);
igInstaller.install(new PackageInstallationSpec().setName("basisprofil.de").setVersion("0.2.40").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL)); igInstaller.install(new PackageInstallationSpec().setName("basisprofil.de").setVersion("0.2.40").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY));
} }
} }

View File

@ -94,6 +94,7 @@ public class NpmTestR4 extends BaseJpaR4Test {
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences()); myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
} }
@Test @Test
public void testCacheDstu3Package() throws Exception { public void testCacheDstu3Package() throws Exception {
byte[] bytes = loadClasspathBytes("/packages/nictiz.fhir.nl.stu3.questionnaires-1.0.2.tgz"); byte[] bytes = loadClasspathBytes("/packages/nictiz.fhir.nl.stu3.questionnaires-1.0.2.tgz");
@ -215,6 +216,10 @@ public class NpmTestR4 extends BaseJpaR4Test {
NpmPackage pkg = myPackageCacheManager.loadPackage("UK.Core.r4", "1.1.0"); NpmPackage pkg = myPackageCacheManager.loadPackage("UK.Core.r4", "1.1.0");
assertEquals(null, pkg.description()); assertEquals(null, pkg.description());
assertEquals("UK.Core.r4", pkg.name()); assertEquals("UK.Core.r4", pkg.name());
// Ensure that we loaded the contents
IBundleProvider searchResult = myStructureDefinitionDao.search(SearchParameterMap.newSynchronous("url", new UriParam("https://fhir.nhs.uk/R4/StructureDefinition/UKCore-Patient")));
assertEquals(1, searchResult.sizeOrThrowNpe());
} }
@Test @Test
@ -262,7 +267,7 @@ public class NpmTestR4 extends BaseJpaR4Test {
PackageInstallOutcomeJson outcome = igInstaller.install(spec); PackageInstallOutcomeJson outcome = igInstaller.install(spec);
ourLog.info("Install messages:\n * {}", outcome.getMessage().stream().collect(Collectors.joining("\n * "))); ourLog.info("Install messages:\n * {}", outcome.getMessage().stream().collect(Collectors.joining("\n * ")));
assertThat(outcome.getMessage(), hasItem("Marking package hl7.fhir.uv.shorthand#0.12.0 as current version")); assertThat(outcome.getMessage(), hasItem("Marking package hl7.fhir.uv.shorthand#0.12.0 as current version"));
assertThat(outcome.getMessage(), hasItem("Indexing Resource[package/CodeSystem-shorthand-code-system.json] with URL: http://hl7.org/fhir/uv/shorthand/CodeSystem/shorthand-code-system|0.12.0")); assertThat(outcome.getMessage(), hasItem("Indexing CodeSystem Resource[package/CodeSystem-shorthand-code-system.json] with URL: http://hl7.org/fhir/uv/shorthand/CodeSystem/shorthand-code-system|0.12.0"));
spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY); spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY);
outcome = igInstaller.install(spec); outcome = igInstaller.install(spec);

View File

@ -31,6 +31,7 @@ import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.utilities.graphql.Argument; import org.hl7.fhir.utilities.graphql.Argument;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices; import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.hl7.fhir.utilities.graphql.Value;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -38,8 +39,11 @@ import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.Matchers.startsWith;
@ -158,7 +162,38 @@ public class JpaGraphQLR4ProviderTest {
" }]\n" + " }]\n" +
" },{\n" + " },{\n" +
" \"name\":[{\n" + " \"name\":[{\n" +
" \"given\":[\"GivenOnlyB1\",\"GivenOnlyB2\"]\n" + " \"given\":[\"pet\",\"GivenOnlyB1\",\"GivenOnlyB2\"]\n" +
" }]\n" +
" }]\n" +
"}" + DATA_SUFFIX), TestUtil.stripWhitespace(responseContent));
assertThat(status.getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue(), startsWith("application/json"));
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
}
@Test
public void testGraphSystemArrayArgumentList() throws Exception {
String query = "{PatientList(id:[\"hapi-123\",\"hapi-124\"]){id,name{family}}}";
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/$graphql?query=" + UrlUtil.escapeUrlParam(query));
CloseableHttpResponse status = ourClient.execute(httpGet);
try {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals(TestUtil.stripWhitespace(DATA_PREFIX + "{\n" +
" \"PatientList\":[{\n" +
" \"id\":\"Patient/hapi-123/_history/2\",\n" +
" \"name\":[{\n" +
" \"family\":\"FAMILY 123\"\n" +
" }]\n" +
" },{\n" +
" \"id\":\"Patient/hapi-124/_history/1\",\n" +
" \"name\":[{\n" +
" \"family\":\"FAMILY 124\"\n" +
" }]\n" + " }]\n" +
" }]\n" + " }]\n" +
"}" + DATA_SUFFIX), TestUtil.stripWhitespace(responseContent)); "}" + DATA_SUFFIX), TestUtil.stripWhitespace(responseContent));
@ -230,24 +265,46 @@ public class JpaGraphQLR4ProviderTest {
ourLog.info("listResources of {} - {}", theType, theSearchParams); ourLog.info("listResources of {} - {}", theType, theSearchParams);
if (theSearchParams.size() == 1) { if (theSearchParams.size() == 1) {
String name = theSearchParams.get(0).getName(); Argument argument = theSearchParams.get(0);
if ("name".equals(name)) {
Patient p = new Patient(); String name = argument.getName();
p.addName() List<String> value = argument.getValues().stream()
.setFamily(theSearchParams.get(0).getValues().get(0).toString()) .map((it) -> it.getValue())
.collect(Collectors.toList());
if ("name".equals(name) && "pet".equals(value.get(0))) {
Patient patient1 = new Patient();
patient1.addName()
.setFamily("pet")
.addGiven("GIVEN1") .addGiven("GIVEN1")
.addGiven("GIVEN2"); .addGiven("GIVEN2");
p.addName() patient1.addName()
.addGiven("GivenOnly1") .addGiven("GivenOnly1")
.addGiven("GivenOnly2"); .addGiven("GivenOnly2");
theMatches.add(p);
p = new Patient(); Patient patient2 = new Patient();
p.addName() patient2.addName()
.addGiven("pet")
.addGiven("GivenOnlyB1") .addGiven("GivenOnlyB1")
.addGiven("GivenOnlyB2"); .addGiven("GivenOnlyB2");
theMatches.add(p);
theMatches.add(patient1);
theMatches.add(patient2);
}
if ("id".equals(name) && Arrays.asList("hapi-123", "hapi-124").containsAll(value)) {
Patient patient1 = new Patient();
patient1.setId("Patient/hapi-123/_history/2");
patient1.addName()
.setFamily("FAMILY 123");
Patient patient2 = new Patient();
patient2.setId("Patient/hapi-124/_history/1");
patient2.addName()
.setFamily("FAMILY 124");
theMatches.add(patient1);
theMatches.add(patient2);
} }
} }
} }

View File

@ -45,23 +45,7 @@ public class ResourceProviderDstu2ValueSetTest extends BaseResourceProviderDstu2
.operation() .operation()
.onInstance(myExtensionalVsId) .onInstance(myExtensionalVsId)
.named("validate-code") .named("validate-code")
.withParameter(Parameters.class, "code", new CodeDt("8495-4")) .withParameter(Parameters.class, "code", new CodeDt("11378-7"))
.andParameter("system", new UriDt("http://loinc.org"))
.execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(new BooleanDt(true), respParam.getParameter().get(0).getValue());
}
@Test
public void testValidateCodeOperationByCodeAndSystemType() {
Parameters respParam = ourClient
.operation()
.onType(ValueSet.class)
.named("validate-code")
.withParameter(Parameters.class, "code", new CodeDt("8450-9"))
.andParameter("system", new UriDt("http://loinc.org")) .andParameter("system", new UriDt("http://loinc.org"))
.execute(); .execute();

View File

@ -749,6 +749,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
.named("validate-code") .named("validate-code")
.withParameter(Parameters.class, "code", new CodeType("8450-9")) .withParameter(Parameters.class, "code", new CodeType("8450-9"))
.andParameter("system", new UriType("http://acme.org")) .andParameter("system", new UriType("http://acme.org"))
.andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"))
.execute(); .execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
@ -780,11 +781,8 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
assertEquals("result", respParam.getParameter().get(0).getName()); assertEquals("result", respParam.getParameter().get(0).getName());
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("message", respParam.getParameter().get(1).getName()); assertEquals("display", respParam.getParameter().get(1).getName());
assertThat(((StringType) respParam.getParameter().get(1).getValue()).getValue(), Matchers.containsStringIgnoringCase("succeeded")); assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
} }
/** /**
@ -810,11 +808,8 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
assertEquals("result", respParam.getParameter().get(0).getName()); assertEquals("result", respParam.getParameter().get(0).getName());
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("message", respParam.getParameter().get(1).getName()); assertEquals("display", respParam.getParameter().get(1).getName());
assertThat(((StringType) respParam.getParameter().get(1).getValue()).getValue(), Matchers.containsStringIgnoringCase("succeeded")); assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
} }
@AfterEach @AfterEach

View File

@ -202,10 +202,6 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
return names; return names;
} }
protected void waitForActivatedSubscriptionCount(int theSize) throws Exception {
TestUtil.waitForSize(theSize, () -> mySubscriptionRegistry.size());
Thread.sleep(500);
}
@AfterAll @AfterAll
public static void afterClassClearContextBaseResourceProviderR4Test() throws Exception { public static void afterClassClearContextBaseResourceProviderR4Test() throws Exception {

View File

@ -10,9 +10,9 @@ import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.entity.TermValueSetConcept; import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation; import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation;
import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum; import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
@ -54,6 +54,7 @@ import java.util.Optional;
import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM; import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM;
import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_VALUE_SET; import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_VALUE_SET;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.containsStringIgnoringCase; import static org.hamcrest.Matchers.containsStringIgnoringCase;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
@ -61,7 +62,6 @@ import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertSame;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
@ -98,11 +98,11 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
private void persistCodeSystem(CodeSystem theCodeSystem) { private void persistCodeSystem(CodeSystem theCodeSystem) {
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
@Override @Override
protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) {
myExtensionalCsId = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless(); myExtensionalCsId = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless();
} }
}); });
myCodeSystemDao.readEntity(myExtensionalCsId, null).getId(); myCodeSystemDao.readEntity(myExtensionalCsId, null).getId();
} }
@ -156,7 +156,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
createLocalVsWithUnknownCode(codeSystem); createLocalVsWithUnknownCode(codeSystem);
} }
private void createLocalCsAndVs() { private void createLocalCs() {
CodeSystem codeSystem = new CodeSystem(); CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl(URL_MY_CODE_SYSTEM); codeSystem.setUrl(URL_MY_CODE_SYSTEM);
codeSystem.setContent(CodeSystemContentMode.COMPLETE); codeSystem.setContent(CodeSystemContentMode.COMPLETE);
@ -671,8 +671,8 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
.named("$expand") .named("$expand")
.withNoParameters(Parameters.class) .withNoParameters(Parameters.class)
.returnResourceType(ValueSet.class) .returnResourceType(ValueSet.class)
.execute(); .execute();
ourLog.info("Expanded: {}",myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded)); ourLog.info("Expanded: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded));
assertEquals(1, expanded.getExpansion().getContains().size()); assertEquals(1, expanded.getExpansion().getContains().size());
// Update the CodeSystem URL and Codes // Update the CodeSystem URL and Codes
@ -696,12 +696,11 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
.withNoParameters(Parameters.class) .withNoParameters(Parameters.class)
.returnResourceType(ValueSet.class) .returnResourceType(ValueSet.class)
.execute(); .execute();
ourLog.info("Expanded: {}",myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded)); ourLog.info("Expanded: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded));
assertEquals(1, expanded.getExpansion().getContains().size()); assertEquals(1, expanded.getExpansion().getContains().size());
} }
/** /**
* #516 * #516
*/ */
@ -796,7 +795,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
} }
private void validateTermValueSetNotExpanded(String theValueSetName) { private void validateTermValueSetNotExpanded(String theValueSetName) {
runInTransaction(()->{ runInTransaction(() -> {
Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable); Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable);
assertTrue(optionalValueSetByResourcePid.isPresent()); assertTrue(optionalValueSetByResourcePid.isPresent());
@ -814,7 +813,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
} }
private void validateTermValueSetExpandedAndChildren(String theValueSetName, CodeSystem theCodeSystem) { private void validateTermValueSetExpandedAndChildren(String theValueSetName, CodeSystem theCodeSystem) {
runInTransaction(()->{ runInTransaction(() -> {
Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable); Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable);
assertTrue(optionalValueSetByResourcePid.isPresent()); assertTrue(optionalValueSetByResourcePid.isPresent());
@ -906,10 +905,11 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
@Test @Test
public void testValidateCodeOperationByCodeAndSystemInstanceOnType() throws IOException { public void testValidateCodeOperationByCodeAndSystemInstanceOnType() throws IOException {
createLocalCsAndVs(); createLocalCs();
createLocalVsWithIncludeConcept();
String url = ourServerBase + String url = ourServerBase +
"/ValueSet/$validate-code?system=" + "/ValueSet/" + myLocalValueSetId.getIdPart() + "/$validate-code?system=" +
UrlUtil.escapeUrlParam(URL_MY_CODE_SYSTEM) + UrlUtil.escapeUrlParam(URL_MY_CODE_SYSTEM) +
"&code=AA"; "&code=AA";
@ -926,7 +926,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
@Test @Test
public void testValidateCodeOperationByCodeAndSystemInstanceOnInstance() throws IOException { public void testValidateCodeOperationByCodeAndSystemInstanceOnInstance() throws IOException {
createLocalCsAndVs(); createLocalCs();
createLocalVsWithIncludeConcept(); createLocalVsWithIncludeConcept();
String url = ourServerBase + String url = ourServerBase +
@ -953,7 +953,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
Parameters respParam = myClient Parameters respParam = myClient
.operation() .operation()
.onType(ValueSet.class) .onInstance(myExtensionalVsId)
.named("validate-code") .named("validate-code")
.withParameter(Parameters.class, "code", new CodeType("8450-9")) .withParameter(Parameters.class, "code", new CodeType("8450-9"))
.andParameter("system", new UriType("http://acme.org")) .andParameter("system", new UriType("http://acme.org"))
@ -965,6 +965,24 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
} }
@Test
public void testValidateCodeOperationNoValueSetProvided() throws Exception {
loadAndPersistCodeSystemAndValueSet();
try {
myClient
.operation()
.onType(ValueSet.class)
.named("validate-code")
.withParameter(Parameters.class, "code", new CodeType("8450-9"))
.andParameter("system", new UriType("http://acme.org"))
.execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate.", e.getMessage());
}
}
@Test @Test
public void testValidateCodeAgainstBuiltInSystem() { public void testValidateCodeAgainstBuiltInSystem() {
Parameters respParam = myClient Parameters respParam = myClient
@ -983,11 +1001,8 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
assertEquals("result", respParam.getParameter().get(0).getName()); assertEquals("result", respParam.getParameter().get(0).getName());
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("message", respParam.getParameter().get(1).getName()); assertEquals("display", respParam.getParameter().get(1).getName());
assertThat(((StringType) respParam.getParameter().get(1).getValue()).getValue(), containsStringIgnoringCase("succeeded")); assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
} }
@Test @Test
@ -1018,7 +1033,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
ourLog.info("Response: {}", response); ourLog.info("Response: {}", response);
} }
HttpGet validateCodeGet = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?code=ChildAA&_pretty=true"); HttpGet validateCodeGet = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?system=http://mycs&code=ChildAA&_pretty=true");
try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet)) { try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet)) {
String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8); String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response: {}", response); ourLog.info("Response: {}", response);
@ -1026,7 +1041,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
assertEquals(true, output.getParameterBool("result")); assertEquals(true, output.getParameterBool("result"));
} }
HttpGet validateCodeGet2 = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?code=FOO&_pretty=true"); HttpGet validateCodeGet2 = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?system=http://mycs&code=FOO&_pretty=true");
try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet2)) { try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet2)) {
String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8); String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response: {}", response); ourLog.info("Response: {}", response);
@ -1070,7 +1085,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B"); TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B");
cs.getConcepts().add(parentB); cs.getConcepts().add(parentB);
theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table);
return codeSystem; return codeSystem;
} }

View File

@ -205,23 +205,6 @@ public abstract class BaseResourceProviderR5Test extends BaseJpaR5Test {
return names; return names;
} }
protected void waitForActivatedSubscriptionCount(int theSize) throws Exception {
for (int i = 0; ; i++) {
if (i == 10) {
fail("Failed to init subscriptions");
}
try {
mySubscriptionLoader.doSyncSubscriptionsForUnitTest();
break;
} catch (ResourceVersionConflictException e) {
Thread.sleep(250);
}
}
TestUtil.waitForSize(theSize, () -> mySubscriptionRegistry.size());
Thread.sleep(500);
}
@AfterAll @AfterAll
public static void afterClassClearContextBaseResourceProviderR5Test() throws Exception { public static void afterClassClearContextBaseResourceProviderR5Test() throws Exception {
JettyUtil.closeServer(ourServer); JettyUtil.closeServer(ourServer);

View File

@ -10,10 +10,10 @@ import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.entity.TermValueSetConcept; import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation; import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation;
import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum; import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
@ -53,6 +53,7 @@ import java.util.Optional;
import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM; import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM;
import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_VALUE_SET; import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_VALUE_SET;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.containsStringIgnoringCase; import static org.hamcrest.Matchers.containsStringIgnoringCase;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
@ -60,7 +61,6 @@ import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertSame;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
@ -983,8 +983,8 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
.named("$expand") .named("$expand")
.withNoParameters(Parameters.class) .withNoParameters(Parameters.class)
.returnResourceType(ValueSet.class) .returnResourceType(ValueSet.class)
.execute(); .execute();
ourLog.info("Expanded: {}",myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded)); ourLog.info("Expanded: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded));
assertEquals(1, expanded.getExpansion().getContains().size()); assertEquals(1, expanded.getExpansion().getContains().size());
// Update the CodeSystem URL and Codes // Update the CodeSystem URL and Codes
@ -1008,12 +1008,11 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
.withNoParameters(Parameters.class) .withNoParameters(Parameters.class)
.returnResourceType(ValueSet.class) .returnResourceType(ValueSet.class)
.execute(); .execute();
ourLog.info("Expanded: {}",myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded)); ourLog.info("Expanded: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded));
assertEquals(1, expanded.getExpansion().getContains().size()); assertEquals(1, expanded.getExpansion().getContains().size());
} }
/** /**
* #516 * #516
*/ */
@ -1108,7 +1107,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
} }
private void validateTermValueSetNotExpanded(String theValueSetName) { private void validateTermValueSetNotExpanded(String theValueSetName) {
runInTransaction(()->{ runInTransaction(() -> {
Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable); Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable);
assertTrue(optionalValueSetByResourcePid.isPresent()); assertTrue(optionalValueSetByResourcePid.isPresent());
@ -1126,7 +1125,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
} }
private void validateTermValueSetExpandedAndChildren(String theValueSetName, CodeSystem theCodeSystem) { private void validateTermValueSetExpandedAndChildren(String theValueSetName, CodeSystem theCodeSystem) {
runInTransaction(()->{ runInTransaction(() -> {
Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable); Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable);
assertTrue(optionalValueSetByResourcePid.isPresent()); assertTrue(optionalValueSetByResourcePid.isPresent());
@ -1219,9 +1218,10 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
@Test @Test
public void testValidateCodeOperationByCodeAndSystemInstanceOnType() throws IOException { public void testValidateCodeOperationByCodeAndSystemInstanceOnType() throws IOException {
createLocalCsAndVs(); createLocalCsAndVs();
createLocalVsWithIncludeConcept();
String url = ourServerBase + String url = ourServerBase +
"/ValueSet/$validate-code?system=" + "/ValueSet/" + myLocalValueSetId.getIdPart() + "/$validate-code?system=" +
UrlUtil.escapeUrlParam(URL_MY_CODE_SYSTEM) + UrlUtil.escapeUrlParam(URL_MY_CODE_SYSTEM) +
"&code=AA"; "&code=AA";
@ -1265,7 +1265,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
Parameters respParam = myClient Parameters respParam = myClient
.operation() .operation()
.onType(ValueSet.class) .onInstance(myExtensionalVsId)
.named("validate-code") .named("validate-code")
.withParameter(Parameters.class, "code", new CodeType("8450-9")) .withParameter(Parameters.class, "code", new CodeType("8450-9"))
.andParameter("system", new UriType("http://acme.org")) .andParameter("system", new UriType("http://acme.org"))
@ -1297,12 +1297,10 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
assertEquals("result", respParam.getParameter().get(0).getName()); assertEquals("result", respParam.getParameter().get(0).getName());
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("message", respParam.getParameter().get(1).getName()); assertEquals("display", respParam.getParameter().get(1).getName());
assertThat(((StringType) respParam.getParameter().get(1).getValue()).getValue(), containsStringIgnoringCase("succeeded")); assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
} }
// Good code and system, but not in specified valueset // Good code and system, but not in specified valueset
{ {
Parameters respParam = myClient Parameters respParam = myClient
@ -1322,7 +1320,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
assertEquals(false, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); assertEquals(false, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("message", respParam.getParameter().get(1).getName()); assertEquals("message", respParam.getParameter().get(1).getName());
assertThat(((StringType) respParam.getParameter().get(1).getValue()).getValue(), containsStringIgnoringCase("Code not found")); assertEquals("Unknown code 'http://hl7.org/fhir/administrative-gender#male'", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
} }
} }
@ -1352,9 +1350,10 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
try (CloseableHttpResponse status = ourHttpClient.execute(expandGet)) { try (CloseableHttpResponse status = ourHttpClient.execute(expandGet)) {
String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8); String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response: {}", response); ourLog.info("Response: {}", response);
assertThat(response, containsString("<display value=\"Child AA\"/>"));
} }
HttpGet validateCodeGet = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?code=ChildAA&_pretty=true"); HttpGet validateCodeGet = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?system=http://mycs&code=ChildAA&_pretty=true");
try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet)) { try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet)) {
String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8); String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response: {}", response); ourLog.info("Response: {}", response);
@ -1362,7 +1361,32 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
assertEquals(true, output.getParameterBool("result")); assertEquals(true, output.getParameterBool("result"));
} }
HttpGet validateCodeGet2 = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?code=FOO&_pretty=true"); HttpGet validateCodeGet2 = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?system=http://mycs&code=FOO&_pretty=true");
try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet2)) {
String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response: {}", response);
Parameters output = myFhirCtx.newXmlParser().parseResource(Parameters.class, response);
assertEquals(false, output.getParameterBool("result"));
}
// Now do a pre-expansion
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
expandGet = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$expand?_pretty=true");
try (CloseableHttpResponse status = ourHttpClient.execute(expandGet)) {
String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response: {}", response);
}
validateCodeGet = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?system=http://mycs&code=ChildAA&_pretty=true");
try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet)) {
String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response: {}", response);
Parameters output = myFhirCtx.newXmlParser().parseResource(Parameters.class, response);
assertEquals(true, output.getParameterBool("result"));
}
validateCodeGet2 = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?system=http://mycs&code=FOO&_pretty=true");
try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet2)) { try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet2)) {
String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8); String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response: {}", response); ourLog.info("Response: {}", response);
@ -1407,7 +1431,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B"); TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B");
cs.getConcepts().add(parentB); cs.getConcepts().add(parentB);
theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table);
return codeSystem; return codeSystem;
} }

View File

@ -508,7 +508,7 @@ public class LastNElasticsearchSvcMultipleObservationsIT {
for (int entryCount = 0; entryCount < 10; entryCount++) { for (int entryCount = 0; entryCount < 10; entryCount++) {
ObservationJson observationJson = new ObservationJson(); ObservationJson observationJson = new ObservationJson();
String identifier = String.valueOf((entryCount + patientCount * 10)); String identifier = String.valueOf((entryCount + patientCount * 10L));
observationJson.setIdentifier(identifier); observationJson.setIdentifier(identifier);
observationJson.setSubject(subject); observationJson.setSubject(subject);
@ -524,7 +524,7 @@ public class LastNElasticsearchSvcMultipleObservationsIT {
assertTrue(elasticsearchSvc.createOrUpdateObservationCodeIndex(codeableConceptId2, codeJson2)); assertTrue(elasticsearchSvc.createOrUpdateObservationCodeIndex(codeableConceptId2, codeJson2));
} }
Date effectiveDtm = new Date(baseObservationDate.getTimeInMillis() - ((10 - entryCount) * 3600 * 1000)); Date effectiveDtm = new Date(baseObservationDate.getTimeInMillis() - ((10L - entryCount) * 3600L * 1000L));
observationJson.setEffectiveDtm(effectiveDtm); observationJson.setEffectiveDtm(effectiveDtm);
assertTrue(elasticsearchSvc.createOrUpdateObservationIndex(identifier, observationJson)); assertTrue(elasticsearchSvc.createOrUpdateObservationIndex(identifier, observationJson));

View File

@ -86,4 +86,7 @@ public class SubscriptionTestUtil {
subscriber.setEmailSender(myEmailSender); subscriber.setEmailSender(myEmailSender);
} }
public int getActiveSubscriptionCount() {
return mySubscriptionRegistry.size();
}
} }

View File

@ -157,6 +157,7 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
Subscription subscription1 = createSubscription(criteria1, payload, ourListenerServerBase); Subscription subscription1 = createSubscription(criteria1, payload, ourListenerServerBase);
Subscription subscription2 = createSubscription(criteria2, payload, ourListenerServerBase); Subscription subscription2 = createSubscription(criteria2, payload, ourListenerServerBase);
waitForActivatedSubscriptionCount(2);
Observation observation1 = sendObservation(code, "SNOMED-CT"); Observation observation1 = sendObservation(code, "SNOMED-CT");
@ -169,7 +170,6 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
ourLog.info("Current interceptors:\n * {}", allInterceptors); ourLog.info("Current interceptors:\n * {}", allInterceptors);
// Should see 1 subscription notification // Should see 1 subscription notification
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations); waitForSize(0, ourCreatedObservations);
waitForSize(1, ourUpdatedObservations); waitForSize(1, ourUpdatedObservations);
@ -181,20 +181,21 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute(); ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute();
waitForQueueToDrain(); waitForQueueToDrain();
ourLog.info("Have {} updates and {} subscriptions - sending observation", ourUpdatedObservations.size(), mySubscriptionTestUtil.getActiveSubscriptionCount());
Observation observation2 = sendObservation(code, "SNOMED-CT"); Observation observation2 = sendObservation(code, "SNOMED-CT");
waitForQueueToDrain();
// Should see one subscription notification // Should see one subscription notification
waitForSize(0, ourCreatedObservations); waitForSize(0, ourCreatedObservations);
waitForSize(3, ourUpdatedObservations); waitForSize(3, ourUpdatedObservations);
// Delet one subscription // Delete one subscription
ourClient.delete().resourceById(new IdDt("Subscription/" + subscription2.getId())).execute(); ourClient.delete().resourceById(new IdDt("Subscription/" + subscription2.getId())).execute();
waitForActivatedSubscriptionCount(1);
ourLog.info("Have {} updates and {} subscriptions - sending observation", ourUpdatedObservations.size(), mySubscriptionTestUtil.getActiveSubscriptionCount());
Observation observationTemp3 = sendObservation(code, "SNOMED-CT"); Observation observationTemp3 = sendObservation(code, "SNOMED-CT");
// Should see only one subscription notification // Should see only one subscription notification
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations); waitForSize(0, ourCreatedObservations);
waitForSize(4, ourUpdatedObservations); waitForSize(4, ourUpdatedObservations);
@ -204,6 +205,7 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
CodingDt coding = codeableConcept.addCoding(); CodingDt coding = codeableConcept.addCoding();
coding.setCode(code + "111"); coding.setCode(code + "111");
coding.setSystem("SNOMED-CT"); coding.setSystem("SNOMED-CT");
ourLog.info("Have {} updates and {} subscriptions - sending observation", ourUpdatedObservations.size(), mySubscriptionTestUtil.getActiveSubscriptionCount());
ourClient.update().resource(observation3).withId(observation3.getIdElement()).execute(); ourClient.update().resource(observation3).withId(observation3.getIdElement()).execute();
// Should see no subscription notification // Should see no subscription notification
@ -218,6 +220,7 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
CodingDt coding1 = codeableConcept1.addCoding(); CodingDt coding1 = codeableConcept1.addCoding();
coding1.setCode(code); coding1.setCode(code);
coding1.setSystem("SNOMED-CT"); coding1.setSystem("SNOMED-CT");
ourLog.info("Have {} updates and {} subscriptions - sending observation", ourUpdatedObservations.size(), mySubscriptionTestUtil.getActiveSubscriptionCount());
ourClient.update().resource(observation3a).withId(observation3a.getIdElement()).execute(); ourClient.update().resource(observation3a).withId(observation3a.getIdElement()).execute();
// Should see only one subscription notification // Should see only one subscription notification

View File

@ -7,6 +7,7 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.HapiExtensions; import ca.uhn.fhir.util.HapiExtensions;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.*;
@ -250,7 +251,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Manually unregister all subscriptions // Manually unregister all subscriptions
mySubscriptionRegistry.unregisterAllSubscriptions(); mySubscriptionRegistry.unregisterAllSubscriptions();
waitForActivatedSubscriptionCount(0); assertEquals(0, mySubscriptionRegistry.size());
// Force a reload // Force a reload
mySubscriptionLoader.doSyncSubscriptionsForUnitTest(); mySubscriptionLoader.doSyncSubscriptionsForUnitTest();

View File

@ -21,9 +21,7 @@ import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.test.utilities.JettyUtil; import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletContextHandler;
@ -42,7 +40,6 @@ import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
/** /**
* Test the rest-hook subscriptions * Test the rest-hook subscriptions
@ -90,23 +87,6 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B
mySubscriptionTestUtil.waitForQueueToDrain(); mySubscriptionTestUtil.waitForQueueToDrain();
} }
protected void waitForActivatedSubscriptionCount(int theSize) throws Exception {
for (int i = 0; ; i++) {
if (i == 10) {
fail("Failed to init subscriptions");
}
try {
mySubscriptionLoader.doSyncSubscriptionsForUnitTest();
break;
} catch (ResourceVersionConflictException e) {
Thread.sleep(250);
}
}
TestUtil.waitForSize(theSize, () -> mySubscriptionRegistry.size());
Thread.sleep(500);
}
private Subscription createSubscription(String criteria, String payload, String endpoint) throws InterruptedException { private Subscription createSubscription(String criteria, String payload, String endpoint) throws InterruptedException {
Subscription subscription = new Subscription(); Subscription subscription = new Subscription();
subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)"); subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)");
@ -232,8 +212,8 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B
Subscription subscription2 = createSubscription(criteria2, payload, ourListenerServerBase); Subscription subscription2 = createSubscription(criteria2, payload, ourListenerServerBase);
runInTransaction(()->{ runInTransaction(() -> {
ourLog.info("All token indexes:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n * "))); ourLog.info("All token indexes:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
}); });
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();

View File

@ -2,10 +2,8 @@ package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.hl7.fhir.dstu3.model.CodeType; import org.hl7.fhir.dstu3.model.CodeType;
import org.hl7.fhir.dstu3.model.Coding; import org.hl7.fhir.dstu3.model.Coding;
@ -13,10 +11,10 @@ import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.PrimitiveType; import org.hl7.fhir.dstu3.model.PrimitiveType;
import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.Type; import org.hl7.fhir.dstu3.model.Type;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -30,12 +28,12 @@ import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.awaitility.Awaitility.await; import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.greaterThan;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test { public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
@ -228,11 +226,13 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
ZipCollectionBuilder files = new ZipCollectionBuilder(); ZipCollectionBuilder files = new ZipCollectionBuilder();
TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files); TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files);
myLoader.loadLoinc(files.getFiles(), mySrd); myLoader.loadLoinc(files.getFiles(), mySrd);
myTerminologyDeferredStorageSvc.saveDeferred();
myTerminologyDeferredStorageSvc.saveDeferred();
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(null, null, new StringType("10013-1"), new StringType(ITermLoaderSvc.LOINC_URI), null, null, null, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(new UriType("http://loinc.org/vs"), null, new StringType("10013-1"), new StringType(ITermLoaderSvc.LOINC_URI), null, null, null, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Found code", result.getMessage()); assertEquals("R' wave amplitude in lead I", result.getDisplay());
} }
@Test @Test
@ -240,11 +240,13 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
ZipCollectionBuilder files = new ZipCollectionBuilder(); ZipCollectionBuilder files = new ZipCollectionBuilder();
TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files); TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files);
myLoader.loadLoinc(files.getFiles(), mySrd); myLoader.loadLoinc(files.getFiles(), mySrd);
myTerminologyDeferredStorageSvc.saveDeferred();
myTerminologyDeferredStorageSvc.saveDeferred();
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(null, null, new StringType("10013-1-9999999999"), new StringType(ITermLoaderSvc.LOINC_URI), null, null, null, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(new UriType("http://loinc.org/vs"), null, new StringType("10013-1-9999999999"), new StringType(ITermLoaderSvc.LOINC_URI), null, null, null, mySrd);
assertFalse(result.isResult()); assertFalse(result.isOk());
assertEquals("Code not found", result.getMessage()); assertEquals("Unknown code {http://loinc.org}10013-1-9999999999 - Unable to expand ValueSet[http://loinc.org/vs]", result.getMessage());
} }
private Set<String> toExpandedCodes(ValueSet theExpanded) { private Set<String> toExpandedCodes(ValueSet theExpanded) {

View File

@ -3,7 +3,6 @@ package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.api.model.TranslationRequest; import ca.uhn.fhir.jpa.api.model.TranslationRequest;
import ca.uhn.fhir.jpa.entity.TermConceptMap; import ca.uhn.fhir.jpa.entity.TermConceptMap;
import ca.uhn.fhir.jpa.entity.TermConceptMapGroup; import ca.uhn.fhir.jpa.entity.TermConceptMapGroup;
@ -43,8 +42,8 @@ import static org.junit.jupiter.api.Assertions.fail;
public class TerminologySvcImplR4Test extends BaseTermR4Test { public class TerminologySvcImplR4Test extends BaseTermR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(TerminologySvcImplR4Test.class); private static final Logger ourLog = LoggerFactory.getLogger(TerminologySvcImplR4Test.class);
ValidationOptions optsNoGuess = new ValidationOptions(); ConceptValidationOptions optsNoGuess = new ConceptValidationOptions();
ValidationOptions optsGuess = new ValidationOptions().guessSystem(); ConceptValidationOptions optsGuess = new ConceptValidationOptions().setInferSystem(true);
private IIdType myConceptMapId; private IIdType myConceptMapId;
private void createAndPersistConceptMap() { private void createAndPersistConceptMap() {
@ -1799,42 +1798,37 @@ public class TerminologySvcImplR4Test extends BaseTermR4Test {
myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
IFhirResourceDaoValueSet.ValidateCodeResult result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, null); IValidationSupport.CodeValidationResult result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, null);
assertNull(result); assertNull(result);
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, "BOGUS", null, null, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, "BOGUS", null, null, null);
assertNull(result); assertFalse(result.isOk());
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, "11378-7", null, null, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, "11378-7", null, null, null);
assertNull(result); assertFalse(result.isOk());
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", null, null, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", null, null, null);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Validation succeeded", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", "Systolic blood pressure at First encounter", null, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", "Systolic blood pressure at First encounter", null, null);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Validation succeeded", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, "http://acme.org", "11378-7", null, null, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, "http://acme.org", "11378-7", null, null, null);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Validation succeeded", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
Coding coding = new Coding("http://acme.org", "11378-7", "Systolic blood pressure at First encounter"); Coding coding = new Coding("http://acme.org", "11378-7", "Systolic blood pressure at First encounter");
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, coding, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, coding, null);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Validation succeeded", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
CodeableConcept codeableConcept = new CodeableConcept(); CodeableConcept codeableConcept = new CodeableConcept();
codeableConcept.addCoding(new Coding("BOGUS", "BOGUS", "BOGUS")); codeableConcept.addCoding(new Coding("BOGUS", "BOGUS", "BOGUS"));
codeableConcept.addCoding(coding); codeableConcept.addCoding(coding);
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, codeableConcept); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, codeableConcept);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Validation succeeded", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -1852,43 +1846,38 @@ public class TerminologySvcImplR4Test extends BaseTermR4Test {
myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
IFhirResourceDaoValueSet.ValidateCodeResult result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, null); IValidationSupport.CodeValidationResult result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, null);
assertNull(result); assertNull(result);
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, "BOGUS", null, null, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, "BOGUS", null, null, null);
assertNull(result); assertFalse(result.isOk());
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, "11378-7", null, null, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, "11378-7", null, null, null);
assertNull(result); assertFalse(result.isOk());
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", null, null, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", null, null, null);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Validation succeeded", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", "Systolic blood pressure at First encounter", null, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", "Systolic blood pressure at First encounter", null, null);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Validation succeeded", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, "http://acme.org", "11378-7", null, null, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, "http://acme.org", "11378-7", null, null, null);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Validation succeeded", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
Coding coding = new Coding("http://acme.org", "11378-7", "Systolic blood pressure at First encounter"); Coding coding = new Coding("http://acme.org", "11378-7", "Systolic blood pressure at First encounter");
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, coding, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, coding, null);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Validation succeeded", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
CodeableConcept codeableConcept = new CodeableConcept(); CodeableConcept codeableConcept = new CodeableConcept();
codeableConcept.addCoding(new Coding("BOGUS", "BOGUS", "BOGUS")); codeableConcept.addCoding(new Coding("BOGUS", "BOGUS", "BOGUS"));
codeableConcept.addCoding(coding); codeableConcept.addCoding(coding);
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, codeableConcept); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, codeableConcept);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Validation succeeded", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
} }

View File

@ -0,0 +1,55 @@
{
"resourceType": "ValueSet",
"id": "2.16.840.1.113883.2.4.3.11.60.40.2.20.5.2--20171231000000",
"meta": {
"profile": [
"http://hl7.org/fhir/StructureDefinition/shareablevalueset"
]
},
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/resource-effectivePeriod",
"valuePeriod": {
"start": "2017-12-31T00:00:00+02:00"
}
}
],
"url": "http://decor.nictiz.nl/fhir/ValueSet/2.16.840.1.113883.2.4.3.11.60.40.2.20.5.2--20171231000000",
"identifier": [
{
"use": "official",
"system": "http://art-decor.org/ns/oids/vs",
"value": "2.16.840.1.113883.2.4.3.11.60.40.2.20.5.2"
}
],
"version": "2017-12-31T00:00:00",
"name": "LandISOCodelijst",
"title": "LandISOCodelijst",
"status": "active",
"experimental": false,
"publisher": "Registratie aan de bron",
"contact": [
{
"name": "Registratie aan de bron",
"telecom": [
{
"system": "url",
"value": "https://www.registratieaandebron.nl"
},
{
"system": "url",
"value": "https://zibs.nl"
}
]
}
],
"description": "ISO 3166-1 (alpha-2) - Alle waarden",
"immutable": false,
"compose": {
"include": [
{
"system": "urn:iso:std:iso:3166"
}
]
}
}

View File

@ -26,6 +26,10 @@
<code value="8450-9" /> <code value="8450-9" />
<display value="Systolic blood pressure--expiration" /> <display value="Systolic blood pressure--expiration" />
</concept> </concept>
<concept>
<code value="11378-7" />
<display value="Systolic blood pressure at First encounter" />
</concept>
</codeSystem> </codeSystem>
<compose> <compose>
<include> <include>
@ -124,4 +128,4 @@
</concept> </concept>
</include> </include>
</compose> </compose>
</ValueSet> </ValueSet>

View File

@ -74,7 +74,11 @@ public class EmpiResourceDaoSvc {
} }
public DaoMethodOutcome updatePerson(IAnyResource thePerson) { public DaoMethodOutcome updatePerson(IAnyResource thePerson) {
return myPersonDao.update(thePerson); if (thePerson.getIdElement().hasIdPart()) {
return myPersonDao.update(thePerson);
} else {
return myPersonDao.create(thePerson);
}
} }
public IAnyResource readPersonByPid(ResourcePersistentId thePersonPid) { public IAnyResource readPersonByPid(ResourcePersistentId thePersonPid) {

View File

@ -35,6 +35,7 @@ public abstract class BaseMigrator implements IMigrator {
private boolean myDryRun; private boolean myDryRun;
private boolean myNoColumnShrink; private boolean myNoColumnShrink;
private boolean myOutOfOrderPermitted; private boolean myOutOfOrderPermitted;
private boolean mySchemaWasInitialized;
private DriverTypeEnum myDriverType; private DriverTypeEnum myDriverType;
private DataSource myDataSource; private DataSource myDataSource;
private List<BaseTask.ExecutedStatement> myExecutedStatements = new ArrayList<>(); private List<BaseTask.ExecutedStatement> myExecutedStatements = new ArrayList<>();
@ -111,4 +112,13 @@ public abstract class BaseMigrator implements IMigrator {
} }
return statementBuilder; return statementBuilder;
} }
public boolean isSchemaWasInitialized() {
return mySchemaWasInitialized;
}
public BaseMigrator setSchemaWasInitialized(boolean theSchemaWasInitialized) {
mySchemaWasInitialized = theSchemaWasInitialized;
return this;
}
} }

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