Merge branch 'master' into batch-empi-job
This commit is contained in:
commit
6bb92717f5
|
@ -25,16 +25,6 @@ jobs:
|
|||
inputs:
|
||||
targetType: 'inline'
|
||||
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
|
||||
env:
|
||||
JAVA_HOME_11_X64: /usr/local/openjdk-11
|
||||
|
|
|
@ -22,9 +22,12 @@ package ca.uhn.fhir.context.phonetic;
|
|||
|
||||
import org.apache.commons.codec.EncoderException;
|
||||
import org.apache.commons.codec.StringEncoder;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.StringJoiner;
|
||||
|
||||
public class ApacheEncoder implements IPhoneticEncoder {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(ApacheEncoder.class);
|
||||
|
||||
|
@ -44,10 +47,44 @@ public class ApacheEncoder implements IPhoneticEncoder {
|
|||
@Override
|
||||
public String encode(String theString) {
|
||||
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);
|
||||
} catch (EncoderException e) {
|
||||
ourLog.error("Failed to encode string " + theString, e);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,9 @@ import org.apache.commons.lang3.builder.ToStringStyle;
|
|||
|
||||
public class ConceptValidationOptions {
|
||||
|
||||
private boolean myValidateDisplay;
|
||||
private boolean myInferSystem;
|
||||
|
||||
public boolean isInferSystem() {
|
||||
return myInferSystem;
|
||||
}
|
||||
|
@ -34,12 +37,19 @@ public class ConceptValidationOptions {
|
|||
return this;
|
||||
}
|
||||
|
||||
private boolean myInferSystem;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
|
||||
.append("inferSystem", myInferSystem)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public boolean isValidateDisplay() {
|
||||
return myValidateDisplay;
|
||||
}
|
||||
|
||||
public ConceptValidationOptions setValidateDisplay(boolean theValidateDisplay) {
|
||||
myValidateDisplay = theValidateDisplay;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ package ca.uhn.fhir.context.support;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import org.thymeleaf.util.Validate;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -29,6 +31,7 @@ public class ValidationSupportContext {
|
|||
private final Set<String> myCurrentlyGeneratingSnapshots = new HashSet<>();
|
||||
|
||||
public ValidationSupportContext(IValidationSupport theRootValidationSupport) {
|
||||
Validate.notNull(theRootValidationSupport, "theRootValidationSupport musty not be null");
|
||||
myRootValidationSupport = theRootValidationSupport;
|
||||
}
|
||||
|
||||
|
|
|
@ -854,6 +854,21 @@ public enum Pointcut {
|
|||
*/
|
||||
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>
|
||||
|
@ -1562,6 +1577,42 @@ public enum Pointcut {
|
|||
"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>
|
||||
* Invoked whenever a persisted Patient/Practitioner resource (a resource that has just been stored in the
|
||||
|
|
|
@ -20,6 +20,9 @@ package ca.uhn.fhir.rest.annotation;
|
|||
* #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.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
@ -32,4 +35,5 @@ import java.lang.annotation.Target;
|
|||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(value= ElementType.METHOD)
|
||||
public @interface GraphQL {
|
||||
RequestTypeEnum type() default RequestTypeEnum.GET;
|
||||
}
|
||||
|
|
|
@ -37,6 +37,6 @@ import java.lang.annotation.Target;
|
|||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.PARAMETER)
|
||||
public @interface GraphQLQuery {
|
||||
public @interface GraphQLQueryBody {
|
||||
// ignore
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -66,6 +66,7 @@ public class Constants {
|
|||
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_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_OCTET_STREAM = "application/octet-stream";
|
||||
public static final String CT_TEXT = "text/plain";
|
||||
|
|
|
@ -37,6 +37,9 @@ public class ResourceVersionConflictException extends BaseServerResponseExceptio
|
|||
public static final int STATUS_CODE = Constants.STATUS_HTTP_409_CONFLICT;
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public ResourceVersionConflictException(String error) {
|
||||
super(STATUS_CODE, error);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ import ca.uhn.fhir.context.RuntimeExtensionDtDefinition;
|
|||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
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.ISupportsUndeclaredExtensions;
|
||||
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 org.apache.commons.lang3.Validate;
|
||||
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.IBaseHasExtensions;
|
||||
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(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 (theTarget instanceof IPrimitiveType<?>) {
|
||||
((IPrimitiveType<?>) theTarget).setValueAsString(((IPrimitiveType<?>) theSource).getValueAsString());
|
||||
|
@ -159,7 +184,13 @@ public class FhirTerser {
|
|||
}
|
||||
|
||||
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);
|
||||
cloneInto(nextValue, target, theIgnoreMissingFields);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -134,14 +134,16 @@ public class CreatePackageCommand extends BaseCommand {
|
|||
}
|
||||
|
||||
String[] dependencies = theCommandLine.getOptionValues(DEPENDENCY_OPT);
|
||||
for (String nextDependencyString : dependencies) {
|
||||
int colonIdx = nextDependencyString.indexOf(":");
|
||||
if (colonIdx == -1) {
|
||||
throw new ParseException("Invalid dependency spec: " + nextDependencyString);
|
||||
if (dependencies != null) {
|
||||
for (String nextDependencyString : dependencies) {
|
||||
int colonIdx = nextDependencyString.indexOf(":");
|
||||
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();
|
||||
|
|
|
@ -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 {
|
||||
try (FileWriter w = new FileWriter(new File(myWorkDirectory, theFileName), false)) {
|
||||
myContext.newJsonParser().encodeResourceToWriter(theResource, w);
|
||||
|
|
|
@ -53,9 +53,9 @@ public class ServerOperations {
|
|||
|
||||
ourLog.info("Received call with content type {} and {} bytes", contentType, bytes.length);
|
||||
|
||||
theServletResponse.setContentType(contentType);
|
||||
theServletResponse.getOutputStream().write(bytes);
|
||||
theServletResponse.getOutputStream().close();
|
||||
theServletResponse.setContentType("text/plain");
|
||||
theServletResponse.getWriter().write("hello");
|
||||
theServletResponse.getWriter().close();
|
||||
}
|
||||
//END SNIPPET: manualInputAndOutput
|
||||
|
||||
|
|
|
@ -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!"
|
|
@ -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!"
|
|
@ -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."
|
|
@ -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.
|
|
@ -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.
|
||||
|
|
@ -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."
|
|
@ -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 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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
||||
<a name="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 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
|
||||
```
|
||||
|
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.api.dao;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
@ -41,31 +42,6 @@ public interface IFhirResourceDaoValueSet<T extends IBaseResource, CD, CC> exten
|
|||
|
||||
void purgeCaches();
|
||||
|
||||
ValidateCodeResult 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;
|
||||
}
|
||||
}
|
||||
IValidationSupport.CodeValidationResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, CD theCoding, CC theCodeableConcept, RequestDetails theRequestDetails);
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -163,8 +163,6 @@
|
|||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>net.ttddyy</groupId>
|
||||
<artifactId>datasource-proxy</artifactId>
|
||||
|
@ -734,7 +732,8 @@
|
|||
<version>dstu2</version>
|
||||
<configPackageBase>ca.uhn.fhir.jpa.config</configPackageBase>
|
||||
<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/>
|
||||
<excludeResourceNames>
|
||||
<!-- <excludeResourceName>OperationDefinition</excludeResourceName> <excludeResourceName>OperationOutcome</excludeResourceName> -->
|
||||
|
@ -750,7 +749,8 @@
|
|||
<version>dstu3</version>
|
||||
<configPackageBase>ca.uhn.fhir.jpa.config</configPackageBase>
|
||||
<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>
|
||||
<excludeResourceNames>
|
||||
</excludeResourceNames>
|
||||
|
@ -765,7 +765,8 @@
|
|||
<version>r4</version>
|
||||
<configPackageBase>ca.uhn.fhir.jpa.config</configPackageBase>
|
||||
<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>
|
||||
<excludeResourceNames>
|
||||
</excludeResourceNames>
|
||||
|
@ -780,7 +781,8 @@
|
|||
<version>r5</version>
|
||||
<configPackageBase>ca.uhn.fhir.jpa.config</configPackageBase>
|
||||
<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>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
|
|
@ -28,6 +28,8 @@ import org.springframework.batch.core.JobParameters;
|
|||
import org.springframework.batch.core.JobParametersInvalidException;
|
||||
import org.springframework.batch.core.JobParametersValidator;
|
||||
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.Optional;
|
||||
|
@ -38,6 +40,8 @@ import java.util.Optional;
|
|||
public class BulkExportJobParameterValidator implements JobParametersValidator {
|
||||
@Autowired
|
||||
private IBulkExportJobDao myBulkExportJobDao;
|
||||
@Autowired
|
||||
private PlatformTransactionManager myTransactionManager;
|
||||
|
||||
@Override
|
||||
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]");
|
||||
}
|
||||
|
||||
StringBuilder errorBuilder = new StringBuilder();
|
||||
Long readChunkSize = theJobParameters.getLong("readChunkSize");
|
||||
if (readChunkSize == null || readChunkSize < 1) {
|
||||
errorBuilder.append("There must be a valid number for readChunkSize, which is at least 1. ");
|
||||
}
|
||||
String jobUUID = theJobParameters.getString("jobUUID");
|
||||
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");
|
||||
});
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
|
||||
String errorMessage = txTemplate.execute(tx -> {
|
||||
StringBuilder errorBuilder = new StringBuilder();
|
||||
Long readChunkSize = theJobParameters.getLong("readChunkSize");
|
||||
if (readChunkSize == null || readChunkSize < 1) {
|
||||
errorBuilder.append("There must be a valid number for readChunkSize, which is at least 1. ");
|
||||
}
|
||||
|
||||
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);
|
||||
String jobUUID = theJobParameters.getString("jobUUID");
|
||||
Optional<BulkExportJobEntity> oJob = myBulkExportJobDao.findByJobId(jobUUID);
|
||||
if (!StringUtils.isBlank(jobUUID) && !oJob.isPresent()) {
|
||||
errorBuilder.append("There is no persisted job that exists with UUID: " + jobUUID + ". ");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
String errorMessage = errorBuilder.toString();
|
||||
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 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)) {
|
||||
throw new JobParametersInvalidException(errorMessage);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
|||
import ca.uhn.fhir.jpa.dao.SearchBuilder;
|
||||
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
|
||||
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.graphql.JpaStorageServices;
|
||||
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
|
||||
|
@ -304,6 +305,11 @@ public abstract class BaseConfig {
|
|||
return new PersistenceExceptionTranslationPostProcessor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HapiTransactionService hapiTransactionService() {
|
||||
return new HapiTransactionService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IInterceptorService jpaInterceptorService() {
|
||||
return new InterceptorService();
|
||||
|
|
|
@ -26,7 +26,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
|||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.StaleStateException;
|
||||
import org.hibernate.PessimisticLockException;
|
||||
import org.hibernate.exception.ConstraintViolationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -35,6 +35,7 @@ import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
|
|||
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
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.
|
||||
*/
|
||||
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"));
|
||||
}
|
||||
if (constraintName.toUpperCase().contains(ResourceIndexedCompositeStringUnique.IDX_IDXCMPSTRUNIQ_STRING)) {
|
||||
if (constraintName.contains(ResourceIndexedCompositeStringUnique.IDX_IDXCMPSTRUNIQ_STRING)) {
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
@ -102,10 +104,18 @@ public class HapiFhirHibernateJpaDialect extends HibernateJpaDialect {
|
|||
* class in a method called "checkBatched" currently. This can all be tested using the
|
||||
* StressTestR4Test method testMultiThreadedUpdateSameResourceInTransaction()
|
||||
*/
|
||||
if (theException instanceof StaleStateException) {
|
||||
if (theException instanceof org.hibernate.StaleStateException) {
|
||||
String msg = messageToPrepend + myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "resourceVersionConstraintFailure");
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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.server.exceptions.InternalErrorException;
|
||||
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.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||
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.stereotype.Repository;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.persistence.EntityManager;
|
||||
|
|
|
@ -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.ExpungeOptions;
|
||||
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.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.BaseTag;
|
||||
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.search.SearchRuntimeDetails;
|
||||
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.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.PersistedJpaBundleProvider;
|
||||
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
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.primitive.IdDt;
|
||||
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.SimplePreResourceAccessDetails;
|
||||
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.MethodNotAllowedException;
|
||||
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.TransactionTemplate;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.persistence.TypedQuery;
|
||||
|
@ -124,7 +126,6 @@ import java.util.UUID;
|
|||
|
||||
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> {
|
||||
|
||||
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;
|
||||
@Autowired
|
||||
private PartitionSettings myPartitionSettings;
|
||||
@Autowired
|
||||
private HapiTransactionService myTransactionService;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel, RequestDetails theRequest) {
|
||||
StopWatch w = new StopWatch();
|
||||
BaseHasResource entity = readEntity(theId, theRequest);
|
||||
|
@ -197,8 +201,20 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
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
|
||||
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) {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "missingBody");
|
||||
throw new InvalidRequestException(msg);
|
||||
|
@ -220,241 +236,14 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
|
||||
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequestDetails, theResource, getResourceName());
|
||||
return doCreate(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;
|
||||
return doCreateForPostOrPut(theResource, theIfNoneExist, thePerformIndexing, theTransactionDetails, theRequestDetails, requestPartitionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method gets called by {@link #deleteByUrl(String, DeleteConflictList, RequestDetails)} as well as by
|
||||
* transaction processors
|
||||
* Called both for FHIR create (POST) operations (via {@link #doCreateForPost(IBaseResource, String, boolean, TransactionDetails, RequestDetails)}
|
||||
* 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
|
||||
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) {
|
||||
private DaoMethodOutcome doCreateForPostOrPut(T theResource, String theIfNoneExist, boolean thePerformIndexing, TransactionDetails theTransactionDetails, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
|
||||
StopWatch w = new StopWatch();
|
||||
|
||||
preProcessResourceForStorage(theResource);
|
||||
|
@ -579,6 +368,239 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
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) {
|
||||
List<TagDefinition> tags = toTagList(theMetaAdd);
|
||||
|
||||
|
@ -647,7 +669,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
|
||||
@Override
|
||||
@Transactional(propagation = Propagation.SUPPORTS)
|
||||
public ExpungeOutcome forceExpungeInExistingTransaction(IIdType theId, ExpungeOptions theExpungeOptions, RequestDetails theRequest) {
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
|
||||
|
||||
|
@ -694,6 +715,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public IBundleProvider history(Date theSince, Date theUntil, RequestDetails theRequestDetails) {
|
||||
// Notify interceptors
|
||||
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails);
|
||||
|
@ -706,10 +728,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public IBundleProvider history(final IIdType theId, final Date theSince, Date theUntil, RequestDetails theRequest) {
|
||||
// Notify interceptors
|
||||
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, getResourceName(), theId);
|
||||
notifyInterceptors(RestOperationTypeEnum.HISTORY_INSTANCE, requestDetails);
|
||||
if (theRequest != null) {
|
||||
// Notify interceptors
|
||||
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, getResourceName(), theId);
|
||||
notifyInterceptors(RestOperationTypeEnum.HISTORY_INSTANCE, requestDetails);
|
||||
}
|
||||
|
||||
StopWatch w = new StopWatch();
|
||||
|
||||
|
@ -755,6 +780,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public <MT extends IBaseMetaType> MT metaAddOperation(IIdType theResourceId, MT theMetaAdd, RequestDetails theRequest) {
|
||||
// Notify interceptors
|
||||
if (theRequest != null) {
|
||||
|
@ -787,6 +813,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public <MT extends IBaseMetaType> MT metaDeleteOperation(IIdType theResourceId, MT theMetaDel, RequestDetails theRequest) {
|
||||
// Notify interceptors
|
||||
if (theRequest != null) {
|
||||
|
@ -821,6 +848,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public <MT extends IBaseMetaType> MT metaGetOperation(Class<MT> theType, IIdType theId, RequestDetails theRequest) {
|
||||
// Notify interceptors
|
||||
if (theRequest != null) {
|
||||
|
@ -842,6 +870,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public <MT extends IBaseMetaType> MT metaGetOperation(Class<MT> theType, RequestDetails theRequestDetails) {
|
||||
// Notify interceptors
|
||||
if (theRequestDetails != null) {
|
||||
|
@ -859,7 +888,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
@Override
|
||||
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;
|
||||
if (isNotBlank(theConditionalUrl)) {
|
||||
|
||||
|
@ -890,10 +922,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
IBaseResource destination;
|
||||
switch (thePatchType) {
|
||||
case JSON_PATCH:
|
||||
destination = JsonPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody);
|
||||
destination = JsonPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody);
|
||||
break;
|
||||
case XML_PATCH:
|
||||
destination = XmlPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody);
|
||||
destination = XmlPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody);
|
||||
break;
|
||||
case FHIR_PATCH_XML:
|
||||
case FHIR_PATCH_JSON:
|
||||
|
@ -932,6 +964,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public T readByPid(ResourcePersistentId thePid) {
|
||||
StopWatch w = new StopWatch();
|
||||
|
||||
|
@ -950,16 +983,19 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public T read(IIdType theId) {
|
||||
return read(theId, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public T read(IIdType theId, RequestDetails theRequestDetails) {
|
||||
return read(theId, theRequestDetails, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public T read(IIdType theId, RequestDetails theRequest, boolean theDeletedOk) {
|
||||
validateResourceTypeAndThrowInvalidRequestException(theId);
|
||||
|
||||
|
@ -1012,11 +1048,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public BaseHasResource readEntity(IIdType theId, RequestDetails theRequest) {
|
||||
return readEntity(theId, true, theRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId, RequestDetails theRequest) {
|
||||
validateResourceTypeAndThrowInvalidRequestException(theId);
|
||||
|
||||
|
@ -1099,6 +1137,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
@Override
|
||||
public void reindex(T theResource, ResourceTable theEntity) {
|
||||
assert TransactionSynchronizationManager.isActualTransactionActive();
|
||||
|
||||
ourLog.debug("Indexing resource {} - PID {}", theEntity.getIdDt().getValue(), theEntity.getId());
|
||||
if (theResource != null) {
|
||||
CURRENTLY_REINDEXING.put(theResource, Boolean.TRUE);
|
||||
|
@ -1111,11 +1151,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void removeTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm) {
|
||||
removeTag(theId, theTagType, theScheme, theTerm, null);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void removeTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, RequestDetails theRequest) {
|
||||
// Notify interceptors
|
||||
|
@ -1216,26 +1258,28 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
@Override
|
||||
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();
|
||||
SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequest, uuid);
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequest, uuid);
|
||||
|
||||
|
||||
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, getResourceName());
|
||||
try (IResultIterator iter = builder.createQuery(theParams, searchRuntimeDetails, theRequest, requestPartitionId)) {
|
||||
while (iter.hasNext()) {
|
||||
retVal.add(iter.next());
|
||||
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, getResourceName());
|
||||
try (IResultIterator iter = builder.createQuery(theParams, searchRuntimeDetails, theRequest, requestPartitionId)) {
|
||||
while (iter.hasNext()) {
|
||||
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) {
|
||||
|
@ -1303,10 +1347,21 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "missingBody");
|
||||
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();
|
||||
|
||||
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;
|
||||
|
||||
|
@ -1321,7 +1376,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
entity = myEntityManager.find(ResourceTable.class, pid.getId());
|
||||
resourceId = entity.getIdDt();
|
||||
} else {
|
||||
return create(theResource, null, thePerformIndexing, theTransactionDetails, theRequest);
|
||||
return create(resource, null, thePerformIndexing, theTransactionDetails, theRequest);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
|
@ -1330,13 +1385,15 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
* BaseOutcomeReturningMethodBindingWithResourceParam
|
||||
*/
|
||||
resourceId = theResource.getIdElement();
|
||||
assert resourceId != null;
|
||||
assert resourceId.hasIdPart();
|
||||
|
||||
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, getResourceName());
|
||||
try {
|
||||
entity = readEntityLatestVersion(resourceId, requestPartitionId);
|
||||
} catch (ResourceNotFoundException e) {
|
||||
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.
|
||||
*/
|
||||
if (!thePerformIndexing) {
|
||||
theResource.setId(entity.getIdDt().getValue());
|
||||
DaoMethodOutcome outcome = toMethodOutcome(theRequest, entity, theResource).setCreated(wasDeleted);
|
||||
resource.setId(entity.getIdDt().getValue());
|
||||
DaoMethodOutcome outcome = toMethodOutcome(theRequest, entity, resource).setCreated(wasDeleted);
|
||||
outcome.setPreviousResource(oldResource);
|
||||
return outcome;
|
||||
}
|
||||
|
@ -1381,11 +1438,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
/*
|
||||
* Otherwise, we're not in a transaction
|
||||
*/
|
||||
ResourceTable savedEntity = updateInternal(theRequest, theResource, thePerformIndexing, theForceUpdateVersion, entity, resourceId, oldResource, theTransactionDetails);
|
||||
DaoMethodOutcome outcome = toMethodOutcome(theRequest, savedEntity, theResource).setCreated(wasDeleted);
|
||||
ResourceTable savedEntity = updateInternal(theRequest, resource, thePerformIndexing, theForceUpdateVersion, entity, resourceId, oldResource, theTransactionDetails);
|
||||
DaoMethodOutcome outcome = toMethodOutcome(theRequest, savedEntity, resource).setCreated(wasDeleted);
|
||||
|
||||
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());
|
||||
|
|
|
@ -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.DeleteConflictList;
|
||||
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.model.cross.IBasePersistedResource;
|
||||
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.StopWatch;
|
||||
import ca.uhn.fhir.util.UrlUtil;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
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.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
@ -121,6 +122,8 @@ public abstract class BaseTransactionProcessor {
|
|||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||
@Autowired
|
||||
private MatchResourceUrlService myMatchResourceUrlService;
|
||||
@Autowired
|
||||
private HapiTransactionService myHapiTransactionService;
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
|
@ -344,9 +347,6 @@ public abstract class BaseTransactionProcessor {
|
|||
final TransactionDetails transactionDetails = new TransactionDetails();
|
||||
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);
|
||||
|
||||
// Do all entries have a verb?
|
||||
|
@ -403,13 +403,16 @@ public abstract class BaseTransactionProcessor {
|
|||
* heavy load with lots of concurrent transactions using all available
|
||||
* database connections.
|
||||
*/
|
||||
TransactionTemplate txManager = new TransactionTemplate(myTxManager);
|
||||
Map<IBase, IBasePersistedResource> entriesToProcess = txManager.execute(status -> {
|
||||
TransactionCallback<Map<IBase, IBasePersistedResource>> txCallback = 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);
|
||||
|
||||
transactionStopWatch.startTask("Commit writes to database");
|
||||
return retVal;
|
||||
});
|
||||
};
|
||||
Map<IBase, IBasePersistedResource> entriesToProcess = myHapiTransactionService.execute(theRequestDetails, txCallback);
|
||||
transactionStopWatch.endCurrentTask();
|
||||
|
||||
for (Map.Entry<IBase, IBasePersistedResource> nextEntry : entriesToProcess.entrySet()) {
|
||||
|
|
|
@ -57,6 +57,8 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
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.isNotBlank;
|
||||
|
||||
|
@ -291,102 +293,14 @@ public class FhirResourceDaoValueSetDstu2 extends BaseHapiFhirResourceDao<ValueS
|
|||
// nothing
|
||||
}
|
||||
|
||||
private String toStringOrNull(IPrimitiveType<String> thePrimitive) {
|
||||
public static String toStringOrNull(IPrimitiveType<String> thePrimitive) {
|
||||
return thePrimitive != null ? thePrimitive.getValue() : null;
|
||||
}
|
||||
|
||||
@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) {
|
||||
List<IIdType> valueSetIds;
|
||||
|
||||
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;
|
||||
return myTerminologySvc.validateCode(vsValidateCodeOptions(), theId, toStringOrNull(theValueSetIdentifier), toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.DeleteConflictList;
|
||||
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.model.cross.IBasePersistedResource;
|
||||
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.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -109,14 +109,13 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
|
|||
private DaoRegistry myDaoRegistry;
|
||||
@Autowired
|
||||
private MatchResourceUrlService myMatchResourceUrlService;
|
||||
@Autowired
|
||||
private HapiTransactionService myHapiTransactionalService;
|
||||
|
||||
private Bundle batch(final RequestDetails theRequestDetails, Bundle theRequest) {
|
||||
ourLog.info("Beginning batch with {} resources", theRequest.getEntry().size());
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
|
||||
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
|
||||
Bundle resp = new Bundle();
|
||||
resp.setType(BundleTypeEnum.BATCH_RESPONSE);
|
||||
|
||||
|
@ -144,7 +143,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
|
|||
// create their own
|
||||
nextResponseBundle = callback.doInTransaction(null);
|
||||
} else {
|
||||
nextResponseBundle = txTemplate.execute(callback);
|
||||
nextResponseBundle = myHapiTransactionalService.execute(theRequestDetails, callback);
|
||||
}
|
||||
caughtEx = null;
|
||||
|
||||
|
|
|
@ -90,6 +90,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.persistence.EntityManager;
|
||||
|
@ -245,6 +246,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
@Override
|
||||
public Iterator<Long> createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest, @Nonnull RequestPartitionId theRequestPartitionId) {
|
||||
assert theRequestPartitionId != null;
|
||||
assert TransactionSynchronizationManager.isActualTransactionActive();
|
||||
|
||||
init(theParams, theSearchUuid, theRequestPartitionId);
|
||||
|
||||
|
@ -263,6 +265,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
@Override
|
||||
public IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntimeDetails, RequestDetails theRequest, @Nonnull RequestPartitionId theRequestPartitionId) {
|
||||
assert theRequestPartitionId != null;
|
||||
assert TransactionSynchronizationManager.isActualTransactionActive();
|
||||
|
||||
init(theParams, theSearchRuntimeDetails.getSearchUuid(), theRequestPartitionId);
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.dstu3;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.support.ConceptValidationOptions;
|
||||
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
|
||||
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||
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.model.cross.IBasePersistedResource;
|
||||
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.util.LogicUtil;
|
||||
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.InvalidRequestException;
|
||||
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.CodeableConcept;
|
||||
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.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.utilities.validation.ValidationOptions;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
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 org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
@ -81,7 +79,7 @@ public class FhirResourceDaoValueSetDstu3 extends BaseHapiFhirResourceDao<ValueS
|
|||
@Override
|
||||
public void start() {
|
||||
super.start();
|
||||
myValidationSupport = getApplicationContext().getBean(IValidationSupport.class,"myJpaValidationSupportChain" );
|
||||
myValidationSupport = getApplicationContext().getBean(IValidationSupport.class, "myJpaValidationSupportChain");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -264,108 +262,10 @@ public class FhirResourceDaoValueSetDstu3 extends BaseHapiFhirResourceDao<ValueS
|
|||
}
|
||||
|
||||
@Override
|
||||
public IFhirResourceDaoValueSet.ValidateCodeResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode,
|
||||
IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding,
|
||||
CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) {
|
||||
|
||||
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;
|
||||
public IValidationSupport.CodeValidationResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode,
|
||||
IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding,
|
||||
CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) {
|
||||
return myTerminologySvc.validateCode(vsValidateCodeOptions(), theId, toStringOrNull(theValueSetIdentifier), toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -395,4 +295,8 @@ public class FhirResourceDaoValueSetDstu3 extends BaseHapiFhirResourceDao<ValueS
|
|||
return retVal;
|
||||
}
|
||||
|
||||
public static ConceptValidationOptions vsValidateCodeOptions() {
|
||||
return new ConceptValidationOptions().setValidateDisplay(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -133,6 +133,8 @@ public class IdHelperService {
|
|||
*/
|
||||
@Nonnull
|
||||
public ResourcePersistentId resolveResourcePersistentIds(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, String theId) {
|
||||
Validate.notNull(theId, "theId must not be null");
|
||||
|
||||
Long retVal;
|
||||
if (myDaoConfig.getResourceClientIdStrategy() == DaoConfig.ClientIdStrategyEnum.ANY || !isValidPid(theId)) {
|
||||
if (myDaoConfig.isDeleteEnabled()) {
|
||||
|
|
|
@ -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
|
||||
return from.get("mySourcePath").in(path);
|
||||
}
|
||||
|
|
|
@ -366,12 +366,12 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
|
|||
break;
|
||||
}
|
||||
|
||||
Predicate predicate;
|
||||
if (values.size() == 1) {
|
||||
predicate = myCriteriaBuilder.equal(hashField, values.get(0));
|
||||
} else {
|
||||
predicate = hashField.in(values);
|
||||
}
|
||||
/*
|
||||
* 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
|
||||
* was more than 1. This caused a performance regression for some reason in Postgres though. So maybe simpler
|
||||
* is better..
|
||||
*/
|
||||
Predicate predicate = hashField.in(values);
|
||||
|
||||
if (theModifier == TokenParamModifier.NOT) {
|
||||
Predicate identityPredicate = theBuilder.equal(theFrom.get("myHashIdentity").as(Long.class), BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName));
|
||||
|
|
|
@ -31,7 +31,6 @@ import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
|
|||
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.util.LogicUtil;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
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.FilterOperator;
|
||||
import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent;
|
||||
import org.hl7.fhir.utilities.validation.ValidationOptions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
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.isNotBlank;
|
||||
|
||||
|
@ -243,108 +242,11 @@ public class FhirResourceDaoValueSetR4 extends BaseHapiFhirResourceDao<ValueSet>
|
|||
}
|
||||
|
||||
@Override
|
||||
public ValidateCodeResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode,
|
||||
IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding,
|
||||
CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) {
|
||||
public IValidationSupport.CodeValidationResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode,
|
||||
IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding,
|
||||
CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) {
|
||||
|
||||
List<IIdType> valueSetIds = Collections.emptyList();
|
||||
|
||||
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;
|
||||
return myTerminologySvc.validateCode(vsValidateCodeOptions(), theId, toStringOrNull(theValueSetIdentifier), toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.r5;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.support.ConceptValidationOptions;
|
||||
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||
import ca.uhn.fhir.context.support.ValidationSupportContext;
|
||||
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
|
||||
|
@ -55,6 +56,8 @@ import java.util.Collections;
|
|||
import java.util.Date;
|
||||
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 org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
@ -245,108 +248,10 @@ public class FhirResourceDaoValueSetR5 extends BaseHapiFhirResourceDao<ValueSet>
|
|||
}
|
||||
|
||||
@Override
|
||||
public ValidateCodeResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode,
|
||||
IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding,
|
||||
CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) {
|
||||
|
||||
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;
|
||||
public IValidationSupport.CodeValidationResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode,
|
||||
IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding,
|
||||
CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) {
|
||||
return myTerminologySvc.validateCode(vsValidateCodeOptions(), theId, toStringOrNull(theValueSetIdentifier), toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -44,6 +44,7 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
|
@ -25,15 +25,24 @@ import ca.uhn.fhir.context.RuntimeSearchParam;
|
|||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
||||
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.rest.api.server.IBundleProvider;
|
||||
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.NumberOrListParam;
|
||||
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.ReferenceOrListParam;
|
||||
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.StringAndListParam;
|
||||
import ca.uhn.fhir.rest.param.StringOrListParam;
|
||||
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.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
|
||||
|
@ -56,6 +65,7 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
for (Value nextValue : nextArgument.getValues()) {
|
||||
String value = nextValue.getValue();
|
||||
IQueryParameterOr<?> queryParam;
|
||||
|
||||
IQueryParameterType param = null;
|
||||
switch (searchParam.getParamType()) {
|
||||
case NUMBER:
|
||||
param = new NumberParam(value);
|
||||
break;
|
||||
case DATE:
|
||||
param = new DateParam(value);
|
||||
break;
|
||||
case STRING:
|
||||
param = new StringParam(value);
|
||||
break;
|
||||
case TOKEN:
|
||||
param = new TokenParam(null, value);
|
||||
break;
|
||||
case REFERENCE:
|
||||
param = new ReferenceParam(value);
|
||||
break;
|
||||
case COMPOSITE:
|
||||
throw new InvalidRequestException("Composite parameters are not yet supported in GraphQL");
|
||||
case QUANTITY:
|
||||
param = new QuantityParam(value);
|
||||
break;
|
||||
case SPECIAL:
|
||||
param = new SpecialParam().setValue(value);
|
||||
break;
|
||||
case URI:
|
||||
break;
|
||||
case HAS:
|
||||
break;
|
||||
}
|
||||
|
||||
params.add(searchParamName, param);
|
||||
switch (searchParam.getParamType()) {
|
||||
case NUMBER:
|
||||
NumberOrListParam numberOrListParam = new NumberOrListParam();
|
||||
for (Value value: nextArgument.getValues()) {
|
||||
numberOrListParam.addOr(new NumberParam(value.getValue()));
|
||||
}
|
||||
queryParam = numberOrListParam;
|
||||
break;
|
||||
case DATE:
|
||||
DateOrListParam dateOrListParam = new DateOrListParam();
|
||||
for (Value value: nextArgument.getValues()) {
|
||||
dateOrListParam.addOr(new DateParam(value.getValue()));
|
||||
}
|
||||
queryParam = dateOrListParam;
|
||||
break;
|
||||
case STRING:
|
||||
StringOrListParam stringOrListParam = new StringOrListParam();
|
||||
for (Value value: nextArgument.getValues()) {
|
||||
stringOrListParam.addOr(new StringParam(value.getValue()));
|
||||
}
|
||||
queryParam = stringOrListParam;
|
||||
break;
|
||||
case TOKEN:
|
||||
TokenOrListParam tokenOrListParam = new TokenOrListParam();
|
||||
for (Value value: nextArgument.getValues()) {
|
||||
tokenOrListParam.addOr(new TokenParam(value.getValue()));
|
||||
}
|
||||
queryParam = tokenOrListParam;
|
||||
break;
|
||||
case REFERENCE:
|
||||
ReferenceOrListParam referenceOrListParam = new ReferenceOrListParam();
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -297,7 +297,8 @@ public class JpaPackageCache extends BasePackageCacheManager implements IHapiPac
|
|||
}
|
||||
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);
|
||||
ourLog.info("Package[{}#{}] " + msg, thePackageId, packageVersionId);
|
||||
}
|
||||
|
|
|
@ -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.TokenParam;
|
||||
import ca.uhn.fhir.rest.param.UriParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.gson.Gson;
|
||||
|
@ -258,13 +259,14 @@ public class PackageInstallerSvcImpl implements IPackageInstallerSvc {
|
|||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
ArrayList<IBaseResource> resources = new ArrayList<>();
|
||||
for (String file : pkg.getFolders().get("package").listFiles()) {
|
||||
if (file.contains(type)) {
|
||||
List<String> filesForType = pkg.getFolders().get("package").getTypes().get(type);
|
||||
if (filesForType != null) {
|
||||
for (String file : filesForType) {
|
||||
try {
|
||||
byte[] content = pkg.getFolders().get("package").fetchFile(file);
|
||||
resources.add(fhirContext.newJsonParser().parseResource(new String(content)));
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,9 +35,11 @@ import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
|||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.transaction.Transactional;
|
||||
import java.util.HashSet;
|
||||
|
||||
import static ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster.doCallHooks;
|
||||
|
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.provider;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
|
||||
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.server.exceptions.InvalidRequestException;
|
||||
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;
|
||||
|
||||
|
@ -142,21 +145,27 @@ public class BaseJpaResourceProviderValueSetDstu2 extends JpaResourceProviderDst
|
|||
startRequest(theServletRequest);
|
||||
try {
|
||||
IFhirResourceDaoValueSet<ValueSet, CodingDt, CodeableConceptDt> dao = (IFhirResourceDaoValueSet<ValueSet, CodingDt, CodeableConceptDt>) getDao();
|
||||
IFhirResourceDaoValueSet.ValidateCodeResult result = dao.validateCode(theValueSetIdentifier, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
|
||||
Parameters retVal = new Parameters();
|
||||
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;
|
||||
IValidationSupport.CodeValidationResult result = dao.validateCode(theValueSetIdentifier, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
|
||||
return (Parameters) toValidateCodeResult(getContext(), result);
|
||||
} finally {
|
||||
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) {
|
||||
boolean haveOne = false;
|
||||
for (boolean next : theBooleans) {
|
||||
|
|
|
@ -25,9 +25,12 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||
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.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.exceptions.BaseServerResponseException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
@ -107,9 +110,23 @@ public class GraphQLProvider {
|
|||
myStorageServices = theStorageServices;
|
||||
}
|
||||
|
||||
@GraphQL
|
||||
public String processGraphQlRequest(ServletRequestDetails theRequestDetails, @IdParam IIdType theId, @GraphQLQuery String theQuery) {
|
||||
@GraphQL(type=RequestTypeEnum.GET)
|
||||
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();
|
||||
engine.setAppInfo(theRequestDetails);
|
||||
engine.setServices(myStorageServices);
|
||||
|
|
|
@ -20,8 +20,10 @@ package ca.uhn.fhir.jpa.provider.dstu3;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
|
||||
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.Operation;
|
||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||
|
@ -148,16 +150,8 @@ public class BaseJpaResourceProviderValueSetDstu3 extends JpaResourceProviderDst
|
|||
startRequest(theServletRequest);
|
||||
try {
|
||||
IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao();
|
||||
IFhirResourceDaoValueSet.ValidateCodeResult result = dao.validateCode(url, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
|
||||
Parameters retVal = new Parameters();
|
||||
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;
|
||||
IValidationSupport.CodeValidationResult result = dao.validateCode(url, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
|
||||
return (Parameters) BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result);
|
||||
} finally {
|
||||
endRequest(theServletRequest);
|
||||
}
|
||||
|
|
|
@ -20,8 +20,10 @@ package ca.uhn.fhir.jpa.provider.r4;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
|
||||
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.Operation;
|
||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||
|
@ -134,16 +136,8 @@ public class BaseJpaResourceProviderValueSetR4 extends JpaResourceProviderR4<Val
|
|||
startRequest(theServletRequest);
|
||||
try {
|
||||
IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao();
|
||||
IFhirResourceDaoValueSet.ValidateCodeResult result = dao.validateCode(theValueSetUrl, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
|
||||
Parameters retVal = new Parameters();
|
||||
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;
|
||||
IValidationSupport.CodeValidationResult result = dao.validateCode(theValueSetUrl, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
|
||||
return (Parameters) BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result);
|
||||
} finally {
|
||||
endRequest(theServletRequest);
|
||||
}
|
||||
|
|
|
@ -20,8 +20,10 @@ package ca.uhn.fhir.jpa.provider.r5;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
|
||||
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.Operation;
|
||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||
|
@ -134,16 +136,8 @@ public class BaseJpaResourceProviderValueSetR5 extends JpaResourceProviderR5<Val
|
|||
startRequest(theServletRequest);
|
||||
try {
|
||||
IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao();
|
||||
IFhirResourceDaoValueSet.ValidateCodeResult result = dao.validateCode(theValueSetUrl, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
|
||||
Parameters retVal = new Parameters();
|
||||
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;
|
||||
IValidationSupport.CodeValidationResult result = dao.validateCode(theValueSetUrl, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
|
||||
return (Parameters) BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result);
|
||||
} finally {
|
||||
endRequest(theServletRequest);
|
||||
}
|
||||
|
|
|
@ -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.IDao;
|
||||
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.TranslationRequest;
|
||||
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.ITermReadSvc;
|
||||
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.rest.api.Constants;
|
||||
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.ResourceNotFoundException;
|
||||
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.UrlUtil;
|
||||
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.query.dsl.BooleanJunction;
|
||||
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.instance.model.api.IAnyResource;
|
||||
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.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.r4.model.CanonicalType;
|
||||
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.isNoneBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
|
||||
|
||||
public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
|
||||
public static final int DEFAULT_FETCH_SIZE = 250;
|
||||
|
@ -653,16 +659,21 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
|
|||
includedConcepts = theIncludeOrExclude
|
||||
.getConcept()
|
||||
.stream()
|
||||
.map(t->new VersionIndependentConcept(theIncludeOrExclude.getSystem(), t.getCode()))
|
||||
.map(t -> new VersionIndependentConcept(theIncludeOrExclude.getSystem(), t.getCode()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
if (includedConcepts != null) {
|
||||
int foundCount = 0;
|
||||
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()) {
|
||||
addOrRemoveCode(theValueSetCodeAccumulator, theAddedCodes, theAdd, next.getSystem(), next.getCode(), lookup.getCodeDisplay());
|
||||
addOrRemoveCode(theValueSetCodeAccumulator, theAddedCodes, theAdd, nextSystem, next.getCode(), lookup.getCodeDisplay());
|
||||
foundCount++;
|
||||
}
|
||||
}
|
||||
|
@ -1244,8 +1255,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
|
|||
return true;
|
||||
}
|
||||
|
||||
protected IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInPreExpandedValueSet(
|
||||
ValidationOptions theValidationOptions,
|
||||
protected IValidationSupport.CodeValidationResult validateCodeIsInPreExpandedValueSet(
|
||||
ConceptValidationOptions theValidationOptions,
|
||||
ValueSet theValueSet, String theSystem, String theCode, String theDisplay, Coding theCoding, CodeableConcept theCodeableConcept) {
|
||||
|
||||
ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet.hasId(), "ValueSet.id is required");
|
||||
|
@ -1253,7 +1264,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
|
|||
|
||||
List<TermValueSetConcept> concepts = new ArrayList<>();
|
||||
if (isNotBlank(theCode)) {
|
||||
if (theValidationOptions.isGuessSystem()) {
|
||||
if (theValidationOptions.isInferSystem()) {
|
||||
concepts.addAll(myValueSetConceptDao.findByValueSetResourcePidAndCode(valueSetResourcePid.getIdAsLong(), theCode));
|
||||
} else if (isNotBlank(theSystem)) {
|
||||
concepts.addAll(findByValueSetResourcePidSystemAndCode(valueSetResourcePid, theSystem, theCode));
|
||||
|
@ -1271,19 +1282,40 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
|
|||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (TermValueSetConcept concept : concepts) {
|
||||
if (isNotBlank(theDisplay) && theDisplay.equals(concept.getDisplay())) {
|
||||
return new IFhirResourceDaoValueSet.ValidateCodeResult(true, "Validation succeeded", concept.getDisplay());
|
||||
if (theValidationOptions.isValidateDisplay() && concepts.size() > 0) {
|
||||
for (TermValueSetConcept concept : concepts) {
|
||||
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()) {
|
||||
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) {
|
||||
|
@ -1499,6 +1531,13 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
|
|||
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.
|
||||
*/
|
||||
|
@ -1526,17 +1565,28 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
|
|||
if (theConceptMap.hasGroup()) {
|
||||
TermConceptMapGroup termConceptMapGroup;
|
||||
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");
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
termConceptMapGroup = new TermConceptMapGroup();
|
||||
termConceptMapGroup.setConceptMap(termConceptMap);
|
||||
termConceptMapGroup.setSource(group.getSource());
|
||||
termConceptMapGroup.setSource(groupSource);
|
||||
termConceptMapGroup.setSourceVersion(group.getSourceVersion());
|
||||
termConceptMapGroup.setTarget(group.getTarget());
|
||||
termConceptMapGroup.setTarget(groupTarget);
|
||||
termConceptMapGroup.setTargetVersion(group.getTargetVersion());
|
||||
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() {
|
||||
return myDeferredStorageSvc != null && !myDeferredStorageSvc.isStorageQueueEmpty();
|
||||
}
|
||||
|
@ -1997,7 +2099,36 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
|
|||
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);
|
||||
|
||||
// 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);
|
||||
if (pid != null) {
|
||||
if (isValueSetPreExpandedForCodeValidation(valueSet)) {
|
||||
IFhirResourceDaoValueSet.ValidateCodeResult outcome = validateCodeIsInPreExpandedValueSet(new ValidationOptions(), valueSet, theCodeSystem, theCode, null, null, null);
|
||||
if (outcome != null && outcome.isResult()) {
|
||||
return Optional.of(new VersionIndependentConcept(theCodeSystem, theCode));
|
||||
}
|
||||
return validateCodeIsInPreExpandedValueSet(theValidationOptions, valueSet, theCodeSystem, theCode, null, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ValueSet canonicalValueSet = toCanonicalValueSet(valueSet);
|
||||
VersionIndependentConcept wantConcept = new VersionIndependentConcept(theCodeSystem, theCode);
|
||||
ValueSetExpansionOptions expansionOptions = new ValueSetExpansionOptions()
|
||||
.setFailOnMissingCodeSystem(false);
|
||||
CodeValidationResult retVal = null;
|
||||
if (valueSet != null) {
|
||||
retVal = new InMemoryTerminologyServerValidationSupport(myContext).validateCodeInValueSet(theValidationSupportContext, theValidationOptions, theCodeSystem, theCode, theDisplay, valueSet);
|
||||
} 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
|
||||
|
@ -2148,6 +2281,12 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected abstract Coding toCanonicalCoding(@Nullable IBaseDatatype theCoding);
|
||||
|
||||
@Nullable
|
||||
protected abstract CodeableConcept toCanonicalCodeableConcept(@Nullable IBaseDatatype theCodeableConcept);
|
||||
|
||||
public static class Job implements HapiJob {
|
||||
@Autowired
|
||||
private ITermReadSvc myTerminologySvc;
|
||||
|
|
|
@ -20,18 +20,23 @@ package ca.uhn.fhir.jpa.term;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.support.ConceptValidationOptions;
|
||||
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||
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.model.dstu2.composite.CodeableConceptDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
||||
import ca.uhn.fhir.util.VersionIndependentConcept;
|
||||
import org.hl7.fhir.instance.model.api.IBaseDatatype;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
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.utilities.validation.ValidationOptions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -132,8 +137,34 @@ public class TermReadSvcDstu2 extends BaseTermReadSvcImpl {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@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();
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
|||
import ca.uhn.fhir.util.CoverageIgnore;
|
||||
import ca.uhn.fhir.util.ValidateUtil;
|
||||
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.dstu3.model.CodeSystem;
|
||||
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.support.TransactionTemplate;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Optional;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@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
|
||||
public void expandValueSet(ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) {
|
||||
ValueSet valueSetToExpand = (ValueSet) theValueSetToExpand;
|
||||
|
@ -130,35 +147,6 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
|
|||
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
|
||||
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) {
|
||||
return super.lookupCode(theSystem, theCode);
|
||||
|
@ -170,7 +158,7 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
|
|||
}
|
||||
|
||||
@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");
|
||||
ValueSet valueSet = (ValueSet) theValueSet;
|
||||
org.hl7.fhir.r4.model.ValueSet valueSetR4 = convertValueSet(valueSet);
|
||||
|
|
|
@ -5,28 +5,19 @@ import ca.uhn.fhir.context.support.ConceptValidationOptions;
|
|||
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||
import ca.uhn.fhir.context.support.ValidationSupportContext;
|
||||
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.term.api.ITermReadSvcR4;
|
||||
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.IBaseResource;
|
||||
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.utilities.validation.ValidationOptions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import javax.transaction.Transactional;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
|
@ -98,50 +89,29 @@ public class TermReadSvcR4 extends BaseTermReadSvcImpl implements ITermReadSvcR4
|
|||
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
|
||||
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) {
|
||||
return super.lookupCode(theSystem, theCode);
|
||||
}
|
||||
|
||||
@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;
|
||||
Coding coding = (Coding) theCoding;
|
||||
CodeableConcept codeableConcept = (CodeableConcept) theCodeableConcept;
|
||||
Coding coding = toCanonicalCoding(theCoding);
|
||||
CodeableConcept codeableConcept = toCanonicalCodeableConcept(theCodeableConcept);
|
||||
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
|
||||
public boolean isValueSetPreExpandedForCodeValidation(IBaseResource theValueSet) {
|
||||
ValueSet valueSet = (ValueSet) theValueSet;
|
||||
|
|
|
@ -6,17 +6,15 @@ import ca.uhn.fhir.context.support.IValidationSupport;
|
|||
import ca.uhn.fhir.context.support.ValidationSupportContext;
|
||||
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
|
||||
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.term.api.ITermReadSvcR5;
|
||||
import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException;
|
||||
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.instance.model.api.IBaseDatatype;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
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.Coding;
|
||||
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.springframework.beans.factory.annotation.Autowired;
|
||||
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 java.util.Optional;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
|
@ -86,57 +80,18 @@ public class TermReadSvcR5 extends BaseTermReadSvcImpl implements IValidationSup
|
|||
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
|
||||
public FhirContext getFhirContext() {
|
||||
return myContext;
|
||||
}
|
||||
|
||||
@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");
|
||||
ValueSet valueSet = (ValueSet) theValueSet;
|
||||
org.hl7.fhir.r4.model.ValueSet valueSetR4 = toCanonicalValueSet(valueSet);
|
||||
|
||||
Coding coding = (Coding) 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());
|
||||
}
|
||||
org.hl7.fhir.r4.model.Coding codingR4 = toCanonicalCoding(theCoding);
|
||||
|
||||
CodeableConcept codeableConcept = (CodeableConcept) theCodeableConcept;
|
||||
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
|
||||
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);
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
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.ValueSetExpansionOptions;
|
||||
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.entity.TermConcept;
|
||||
import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement;
|
||||
import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
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 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.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.r4.model.CodeSystem;
|
||||
import org.hl7.fhir.r4.model.ConceptMap;
|
||||
|
@ -105,7 +108,12 @@ public interface ITermReadSvc extends IValidationSupport {
|
|||
/**
|
||||
* 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);
|
||||
|
||||
|
@ -114,5 +122,4 @@ public interface ITermReadSvc extends IValidationSupport {
|
|||
*/
|
||||
boolean isValueSetPreExpandedForCodeValidation(IBaseResource theValueSet);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -66,13 +66,13 @@ public class JpaValidationSupportChain extends ValidationSupportChain {
|
|||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
addValidationSupport(new CommonCodeSystemsTerminologyService(myFhirContext));
|
||||
addValidationSupport(myDefaultProfileValidationSupport);
|
||||
addValidationSupport(myJpaValidationSupport);
|
||||
addValidationSupport(myTerminologyService);
|
||||
addValidationSupport(new SnapshotGeneratingValidationSupport(myFhirContext));
|
||||
addValidationSupport(new InMemoryTerminologyServerValidationSupport(myFhirContext));
|
||||
addValidationSupport(myNpmJpaValidationSupport);
|
||||
addValidationSupport(new CommonCodeSystemsTerminologyService(myFhirContext));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
|
|||
import ca.uhn.fhir.validation.IInstanceValidatorModule;
|
||||
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
||||
import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder;
|
||||
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
|
||||
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
import org.hibernate.dialect.H2Dialect;
|
||||
|
|
|
@ -56,6 +56,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
|
|||
private Exception myLastStackTrace;
|
||||
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
public IBatchJobSubmitter batchJobSubmitter() {
|
||||
return new BatchJobSubmitterImpl();
|
||||
|
@ -118,7 +119,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
|
|||
|
||||
retVal.setDriver(new org.h2.Driver());
|
||||
retVal.setUrl("jdbc:h2:mem:testdb_r4");
|
||||
retVal.setMaxWaitMillis(10000);
|
||||
retVal.setMaxWaitMillis(30000);
|
||||
retVal.setUsername("");
|
||||
retVal.setPassword("");
|
||||
retVal.setMaxTotal(ourMaxThreads);
|
||||
|
@ -126,7 +127,8 @@ public class TestR4Config extends BaseJavaConfigR4 {
|
|||
SLF4JLogLevel level = SLF4JLogLevel.INFO;
|
||||
DataSource dataSource = ProxyDataSourceBuilder
|
||||
.create(retVal)
|
||||
.logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
|
||||
// .logQueryBySlf4j(level)
|
||||
.logSlowQueryBySlf4j(10, TimeUnit.SECONDS, level)
|
||||
.beforeQuery(new BlockLargeNumbersOfParamsListener())
|
||||
.afterQuery(captureQueriesListener())
|
||||
.afterQuery(new CurrentThreadCaptureQueriesListener())
|
||||
|
|
|
@ -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.model.ExpungeOptions;
|
||||
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.config.BaseConfig;
|
||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||
import ca.uhn.fhir.jpa.entity.TermConcept;
|
||||
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.reindex.IResourceReindexingSvc;
|
||||
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.MemoryCacheService;
|
||||
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.server.IBundleProvider;
|
||||
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.test.BaseTest;
|
||||
import ca.uhn.fhir.test.utilities.LoggingExtension;
|
||||
|
@ -59,7 +62,6 @@ import org.mockito.Mock;
|
|||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
@ -128,6 +130,10 @@ public abstract class BaseJpaTest extends BaseTest {
|
|||
@Autowired
|
||||
protected IPartitionLookupSvc myPartitionConfigSvc;
|
||||
@Autowired
|
||||
protected SubscriptionRegistry mySubscriptionRegistry;
|
||||
@Autowired
|
||||
protected SubscriptionLoader mySubscriptionLoader;
|
||||
@Autowired
|
||||
private IdHelperService myIdHelperService;
|
||||
@Autowired
|
||||
private MemoryCacheService myMemoryCacheService;
|
||||
|
@ -419,6 +425,23 @@ public abstract class BaseJpaTest extends BaseTest {
|
|||
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
|
||||
public static void beforeClassRandomizeLocale() {
|
||||
randomizeLocale();
|
||||
|
|
|
@ -590,19 +590,27 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
|
|||
@Test
|
||||
public void testDeleteFailsIfIncomingLinks() {
|
||||
String methodName = "testDeleteFailsIfIncomingLinks";
|
||||
SearchParameterMap map;
|
||||
List<IIdType> found;
|
||||
|
||||
Organization org = new Organization();
|
||||
org.setName(methodName);
|
||||
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.addName().addFamily(methodName);
|
||||
patient.getManagingOrganization().setReference(orgId);
|
||||
IIdType patId = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.add("_id", new StringParam(orgId.getIdPart()));
|
||||
map.addRevInclude(new Include("*"));
|
||||
List<IIdType> found = toUnqualifiedVersionlessIds(myOrganizationDao.search(map));
|
||||
found = toUnqualifiedVersionlessIds(myOrganizationDao.search(map));
|
||||
assertThat(found, contains(orgId, patId));
|
||||
|
||||
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);
|
||||
|
||||
map = new SearchParameterMap();
|
||||
map = SearchParameterMap.newSynchronous();
|
||||
map.add("_id", new StringParam(orgId.getIdPart()));
|
||||
map.addRevInclude(new Include("*"));
|
||||
found = toUnqualifiedVersionlessIds(myOrganizationDao.search(map));
|
||||
|
@ -623,7 +643,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
|
|||
|
||||
myOrganizationDao.delete(orgId, mySrd);
|
||||
|
||||
map = new SearchParameterMap();
|
||||
map = SearchParameterMap.newSynchronous();
|
||||
map.add("_id", new StringParam(orgId.getIdPart()));
|
||||
map.addRevInclude(new Include("*"));
|
||||
found = toUnqualifiedVersionlessIds(myOrganizationDao.search(map));
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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.CodingDt;
|
||||
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.StringDt;
|
||||
import ca.uhn.fhir.model.primitive.UriDt;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
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.Test;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -40,30 +38,42 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testValidateCodeOperationByCodeAndSystemBad() {
|
||||
UriDt valueSetIdentifier = null;
|
||||
public void testValidateCodeOperationByCodeAndSystemBadCode() {
|
||||
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://loinc.org");
|
||||
StringDt display = null;
|
||||
CodingDt coding = null;
|
||||
CodeableConceptDt codeableConcept = null;
|
||||
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertFalse(result.isResult());
|
||||
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertFalse(result.isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateCodeOperationByCodeAndSystemGood() {
|
||||
UriDt valueSetIdentifier = null;
|
||||
public void testValidateCodeOperationByCodeAndSystemBadSystem() {
|
||||
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;
|
||||
CodeDt code = new CodeDt("8450-9");
|
||||
UriDt system = new UriDt("http://loinc.org");
|
||||
StringDt display = null;
|
||||
CodingDt coding = null;
|
||||
CodeableConceptDt 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());
|
||||
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertFalse(result.isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -75,8 +85,8 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
|
|||
StringDt display = null;
|
||||
CodingDt coding = null;
|
||||
CodeableConceptDt codeableConcept = null;
|
||||
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isResult());
|
||||
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isOk());
|
||||
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");
|
||||
CodingDt coding = null;
|
||||
CodeableConceptDt codeableConcept = null;
|
||||
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertFalse(result.isResult());
|
||||
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertFalse(result.isOk());
|
||||
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
|
||||
|
@ -103,8 +114,8 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
|
|||
StringDt display = new StringDt("Systolic blood pressure at First encounter");
|
||||
CodingDt coding = null;
|
||||
CodeableConceptDt codeableConcept = null;
|
||||
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isResult());
|
||||
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isOk());
|
||||
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
|
||||
}
|
||||
|
||||
|
@ -117,8 +128,8 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
|
|||
StringDt display = null;
|
||||
CodingDt coding = null;
|
||||
CodeableConceptDt codeableConcept = new CodeableConceptDt("http://loinc.org", "11378-7");
|
||||
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isResult());
|
||||
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isOk());
|
||||
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
|
||||
}
|
||||
|
||||
|
@ -131,8 +142,8 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
|
|||
StringDt display = null;
|
||||
CodingDt coding = null;
|
||||
CodeableConceptDt codeableConcept = null;
|
||||
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isResult());
|
||||
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isOk());
|
||||
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
|
||||
}
|
||||
|
||||
|
|
|
@ -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.ITermConceptMapDao;
|
||||
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.r4.BaseJpaR4Test;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
|
@ -340,6 +341,8 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
|
|||
private IValidationSupport myJpaValidationSupportChainDstu3;
|
||||
@Autowired
|
||||
private IBulkDataExportSvc myBulkDataExportSvc;
|
||||
@Autowired
|
||||
protected ITermValueSetDao myTermValueSetDao;
|
||||
|
||||
@AfterEach()
|
||||
public void afterCleanupDao() {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package ca.uhn.fhir.jpa.dao.dstu3;
|
||||
|
||||
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.util.TestUtil;
|
||||
import org.hl7.fhir.dstu3.model.CodeSystem;
|
||||
import org.hl7.fhir.dstu3.model.CodeType;
|
||||
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.instance.model.api.IIdType;
|
||||
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.Disabled;
|
||||
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
|
||||
@Disabled
|
||||
public void testBuiltInValueSetFetchAndExpand() {
|
||||
|
@ -144,33 +188,6 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
|
|||
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
|
||||
public void testValidateCodeOperationByIdentifierAndCodeAndSystem() {
|
||||
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;
|
||||
Coding coding = null;
|
||||
CodeableConcept codeableConcept = null;
|
||||
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isResult());
|
||||
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isOk());
|
||||
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");
|
||||
Coding coding = null;
|
||||
CodeableConcept codeableConcept = null;
|
||||
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertFalse(result.isResult());
|
||||
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertFalse(result.isOk());
|
||||
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");
|
||||
Coding coding = null;
|
||||
CodeableConcept codeableConcept = null;
|
||||
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isResult());
|
||||
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isOk());
|
||||
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
|
||||
}
|
||||
|
||||
|
@ -222,8 +239,8 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
|
|||
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());
|
||||
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isOk());
|
||||
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
|
||||
}
|
||||
|
||||
|
@ -237,8 +254,8 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
|
|||
Coding coding = null;
|
||||
CodeableConcept codeableConcept = new CodeableConcept();
|
||||
codeableConcept.addCoding().setSystem("http://acme.org").setCode("11378-7");
|
||||
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isResult());
|
||||
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isOk());
|
||||
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 code = new StringType("male");
|
||||
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());
|
||||
assertTrue(result.isResult(), result.getMessage());
|
||||
assertTrue(result.isOk(), result.getMessage());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -447,8 +447,6 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
|
|||
protected ITermConceptMapGroupElementTargetDao myTermConceptMapGroupElementTargetDao;
|
||||
@Autowired
|
||||
protected ICacheWarmingSvc myCacheWarmingSvc;
|
||||
@Autowired
|
||||
protected SubscriptionRegistry mySubscriptionRegistry;
|
||||
protected IServerInterceptor myInterceptor;
|
||||
@Autowired
|
||||
protected DaoRegistry myDaoRegistry;
|
||||
|
|
|
@ -163,7 +163,7 @@ public class BaseR4SearchLastN extends BaseJpaTest {
|
|||
}
|
||||
|
||||
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.
|
||||
return new Date(observationDate.getTimeInMillis() - (milliSecondsPerHour * (theTimeOffset + theObservationIndex)));
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import ca.uhn.fhir.jpa.api.model.TranslationRequest;
|
|||
import ca.uhn.fhir.jpa.api.model.TranslationResult;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
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.Coding;
|
||||
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
|
||||
public void testUploadAndApplyR4DemoConceptMap() throws IOException {
|
||||
|
|
|
@ -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());
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -441,7 +441,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
|
||||
Observation obs = new Observation();
|
||||
obs.getSubject().setReference("Patient/P");
|
||||
myObservationDao.update(obs);
|
||||
myObservationDao.create(obs);
|
||||
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.setLoadSynchronous(true);
|
||||
|
@ -482,7 +482,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
|
||||
Observation obs = new Observation();
|
||||
obs.getSubject().setReference("Patient/P");
|
||||
myObservationDao.update(obs);
|
||||
myObservationDao.create(obs);
|
||||
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.setLoadSynchronous(true);
|
||||
|
|
|
@ -697,7 +697,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
|
|||
myCaptureQueriesListener.logSelectQueries();
|
||||
|
||||
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")));
|
||||
|
||||
}
|
||||
|
|
|
@ -398,7 +398,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
|
|||
unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
|
||||
assertThat(unformattedSql, stringContainsInOrder(
|
||||
"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_TYPE"))));
|
||||
|
@ -535,7 +535,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
|
|||
unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
|
||||
assertThat(unformattedSql, stringContainsInOrder(
|
||||
"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_TYPE"))));
|
||||
|
@ -556,8 +556,8 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
|
|||
assertThat(toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder(srId));
|
||||
unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
|
||||
assertThat(unformattedSql, stringContainsInOrder(
|
||||
"SRC_PATH='ServiceRequest.subject.where(resolve() is Patient)'",
|
||||
"SRC_PATH='ServiceRequest.performer'"
|
||||
"SRC_PATH in ('ServiceRequest.subject.where(resolve() is Patient)')",
|
||||
"SRC_PATH in ('ServiceRequest.performer')"
|
||||
));
|
||||
assertThat(unformattedSql, not(containsString(("RES_DELETED_AT"))));
|
||||
assertThat(unformattedSql, not(containsString(("RES_TYPE"))));
|
||||
|
|
|
@ -9,7 +9,10 @@ import ca.uhn.fhir.jpa.entity.TermValueSet;
|
|||
import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
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.ITermLoaderSvc;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
|
||||
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
|
||||
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.CanonicalType;
|
||||
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.Condition;
|
||||
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.Group;
|
||||
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.Patient;
|
||||
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.QuestionnaireResponse;
|
||||
import org.hl7.fhir.r4.model.Reference;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
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.r5.utils.IResourceValidator;
|
||||
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.junit.jupiter.api.Assertions.assertEquals;
|
||||
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.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
@ -95,6 +103,105 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
|||
@Autowired
|
||||
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
|
||||
*/
|
||||
|
@ -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
|
||||
* <p>
|
||||
|
@ -533,16 +698,10 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
|||
obs.getCode().getCodingFirstRep().setSystem("http://example.com/codesystem");
|
||||
obs.getCode().getCodingFirstRep().setCode("foo-foo");
|
||||
obs.getCode().getCodingFirstRep().setDisplay("Some Code");
|
||||
try {
|
||||
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));
|
||||
fail();
|
||||
} 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());
|
||||
}
|
||||
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));
|
||||
assertEquals("Unknown code in fragment CodeSystem 'http://example.com/codesystem#foo-foo'", outcome.getIssueFirstRep().getDiagnostics());
|
||||
assertEquals(OperationOutcome.IssueSeverity.WARNING, outcome.getIssueFirstRep().getSeverity());
|
||||
|
||||
// Correct codesystem, Code in 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());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
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.dao.IFhirResourceDaoValueSet;
|
||||
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
|
||||
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.IPrimitiveType;
|
||||
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.UriType;
|
||||
import org.hl7.fhir.r4.model.ValueSet;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -49,14 +48,14 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
|
|||
public void before02() throws IOException {
|
||||
ValueSet upload = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml");
|
||||
myExtensionalVsId = myValueSetDao.create(upload, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
|
||||
CodeSystem upload2 = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs.xml");
|
||||
myCodeSystemDao.create(upload2, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateCodeOperationByCodeAndSystemBad() {
|
||||
public void testValidateCodeOperationNoValueSet() {
|
||||
UriType valueSetIdentifier = null;
|
||||
IdType id = null;
|
||||
CodeType code = new CodeType("8450-9-XXX");
|
||||
|
@ -64,22 +63,12 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
|
|||
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());
|
||||
try {
|
||||
myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals("Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate.", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -91,8 +80,8 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
|
|||
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());
|
||||
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isOk());
|
||||
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");
|
||||
Coding coding = null;
|
||||
CodeableConcept codeableConcept = null;
|
||||
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertFalse(result.isResult());
|
||||
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertFalse(result.isOk());
|
||||
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
|
||||
|
@ -119,8 +109,8 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
|
|||
StringType display = new StringType("Systolic blood pressure at First encounter");
|
||||
Coding coding = null;
|
||||
CodeableConcept codeableConcept = null;
|
||||
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isResult());
|
||||
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isOk());
|
||||
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
|
||||
}
|
||||
|
||||
|
@ -134,8 +124,8 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
|
|||
Coding coding = null;
|
||||
CodeableConcept codeableConcept = new CodeableConcept();
|
||||
codeableConcept.addCoding().setSystem("http://acme.org").setCode("11378-7");
|
||||
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isResult());
|
||||
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isOk());
|
||||
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
|
||||
}
|
||||
|
||||
|
@ -151,18 +141,18 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
|
|||
Coding coding = null;
|
||||
CodeableConcept codeableConcept = new CodeableConcept();
|
||||
codeableConcept.addCoding().setSystem("http://acme.org").setCode("11378-7");
|
||||
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isResult());
|
||||
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isOk());
|
||||
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
|
||||
|
||||
myTerminologyDeferredStorageSvc.saveDeferred();
|
||||
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());
|
||||
|
||||
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -175,8 +165,8 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
|
|||
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());
|
||||
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isOk());
|
||||
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
|
||||
}
|
||||
|
||||
|
@ -191,18 +181,18 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
|
|||
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());
|
||||
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
|
||||
assertTrue(result.isOk());
|
||||
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
|
||||
|
||||
myTerminologyDeferredStorageSvc.saveDeferred();
|
||||
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());
|
||||
|
||||
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -213,17 +203,17 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
|
|||
ValueSet expanded = myValueSetDao.expand(myExtensionalVsId, null, mySrd);
|
||||
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
|
||||
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("<contains>"));
|
||||
assertThat(resp, containsString("<system value=\"http://acme.org\"/>"));
|
||||
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("<system value=\"http://acme.org\"/>"));
|
||||
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("</expansion>"));
|
||||
|
||||
|
@ -236,12 +226,12 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
|
|||
ourLog.info(resp);
|
||||
//@formatter:off
|
||||
assertThat(resp, stringContainsInOrder(
|
||||
"<code value=\"11378-7\"/>",
|
||||
"<display value=\"Systolic blood pressure at First encounter\"/>"));
|
||||
"<code value=\"11378-7\"/>",
|
||||
"<display value=\"Systolic blood pressure at First encounter\"/>"));
|
||||
//@formatter:on
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testExpandByValueSet_ExceedsMaxSize() {
|
||||
// Add a bunch of codes
|
||||
|
@ -265,7 +255,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testValidateCodeAgainstBuiltInValueSetAndCodeSystemWithValidCode() {
|
||||
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 code = new StringType("male");
|
||||
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());
|
||||
assertTrue( result.isResult(), result.getMessage());
|
||||
assertTrue(result.isOk(), result.getMessage());
|
||||
assertEquals("Male", result.getDisplay());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1704,15 +1704,14 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
|
|||
|
||||
@Test
|
||||
public void testSearch_TagNotParam_SearchAllPartitions() {
|
||||
IIdType patientIdNull = createPatient(withPartition(null), withActiveTrue(), withTag("http://system", "code"));
|
||||
IIdType patientId1 = createPatient(withPartition(1), 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"), withIdentifier("http://foo", "bar"));
|
||||
IIdType patientId2 = createPatient(withPartition(2), withActiveTrue(), withTag("http://system", "code"));
|
||||
createPatient(withPartition(null), withActiveTrue(), withTag("http://system", "code2"));
|
||||
createPatient(withPartition(1), withActiveTrue(), withTag("http://system", "code2"));
|
||||
createPatient(withPartition(2), withActiveTrue(), withTag("http://system", "code2"));
|
||||
|
||||
addReadAllPartitions();
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
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);
|
||||
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
|
||||
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
|
||||
|
@ -1947,10 +1966,10 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
|
|||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
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);
|
||||
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, "PARTITION_ID"));
|
||||
|
||||
|
@ -1985,10 +2004,10 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
|
|||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
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);
|
||||
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, "PARTITION_ID"));
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ public class SearchWithInterceptorR4Test extends BaseJpaR4Test {
|
|||
String query = list.get(0).getSql(true, false);
|
||||
ourLog.info("Query: {}", query);
|
||||
|
||||
assertThat(query, containsString("HASH_SYS_AND_VALUE='3788488238034018567'"));
|
||||
assertThat(query, containsString("HASH_SYS_AND_VALUE in ('3788488238034018567')"));
|
||||
|
||||
} finally {
|
||||
myInterceptorRegistry.unregisterInterceptor(interceptor);
|
||||
|
|
|
@ -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.CodeableConcept;
|
||||
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.IGraphQLStorageServices;
|
||||
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 java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
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())));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,12 +98,12 @@ public class IgInstallerDstu3Test extends BaseJpaDstu3Test {
|
|||
PackageInstallationSpec spec = new PackageInstallationSpec()
|
||||
.setName("nictiz.fhir.nl.stu3.questionnaires")
|
||||
.setVersion("1.0.2")
|
||||
.setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL)
|
||||
.setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_ONLY)
|
||||
.setFetchDependencies(true)
|
||||
.addDependencyExclude("hl7\\.fhir\\.[a-zA-Z0-9]+\\.core");
|
||||
PackageInstallOutcomeJson outcome = igInstaller.install(spec);
|
||||
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(() -> {
|
||||
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");
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,6 +94,7 @@ public class NpmTestR4 extends BaseJpaR4Test {
|
|||
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCacheDstu3Package() throws Exception {
|
||||
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");
|
||||
assertEquals(null, pkg.description());
|
||||
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
|
||||
|
@ -262,7 +267,7 @@ public class NpmTestR4 extends BaseJpaR4Test {
|
|||
PackageInstallOutcomeJson outcome = igInstaller.install(spec);
|
||||
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("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);
|
||||
outcome = igInstaller.install(spec);
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.hl7.fhir.r4.model.Patient;
|
|||
import org.hl7.fhir.r4.model.Resource;
|
||||
import org.hl7.fhir.utilities.graphql.Argument;
|
||||
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.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
@ -38,8 +39,11 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
|
@ -158,7 +162,38 @@ public class JpaGraphQLR4ProviderTest {
|
|||
" }]\n" +
|
||||
" },{\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" +
|
||||
"}" + DATA_SUFFIX), TestUtil.stripWhitespace(responseContent));
|
||||
|
@ -230,24 +265,46 @@ public class JpaGraphQLR4ProviderTest {
|
|||
ourLog.info("listResources of {} - {}", theType, theSearchParams);
|
||||
|
||||
if (theSearchParams.size() == 1) {
|
||||
String name = theSearchParams.get(0).getName();
|
||||
if ("name".equals(name)) {
|
||||
Patient p = new Patient();
|
||||
p.addName()
|
||||
.setFamily(theSearchParams.get(0).getValues().get(0).toString())
|
||||
Argument argument = theSearchParams.get(0);
|
||||
|
||||
String name = argument.getName();
|
||||
List<String> value = argument.getValues().stream()
|
||||
.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("GIVEN2");
|
||||
p.addName()
|
||||
patient1.addName()
|
||||
.addGiven("GivenOnly1")
|
||||
.addGiven("GivenOnly2");
|
||||
theMatches.add(p);
|
||||
|
||||
p = new Patient();
|
||||
p.addName()
|
||||
Patient patient2 = new Patient();
|
||||
patient2.addName()
|
||||
.addGiven("pet")
|
||||
.addGiven("GivenOnlyB1")
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,23 +45,7 @@ public class ResourceProviderDstu2ValueSetTest extends BaseResourceProviderDstu2
|
|||
.operation()
|
||||
.onInstance(myExtensionalVsId)
|
||||
.named("validate-code")
|
||||
.withParameter(Parameters.class, "code", new CodeDt("8495-4"))
|
||||
.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"))
|
||||
.withParameter(Parameters.class, "code", new CodeDt("11378-7"))
|
||||
.andParameter("system", new UriDt("http://loinc.org"))
|
||||
.execute();
|
||||
|
||||
|
|
|
@ -749,6 +749,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
|
|||
.named("validate-code")
|
||||
.withParameter(Parameters.class, "code", new CodeType("8450-9"))
|
||||
.andParameter("system", new UriType("http://acme.org"))
|
||||
.andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"))
|
||||
.execute();
|
||||
|
||||
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(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
|
||||
|
||||
assertEquals("message", respParam.getParameter().get(1).getName());
|
||||
assertThat(((StringType) respParam.getParameter().get(1).getValue()).getValue(), Matchers.containsStringIgnoringCase("succeeded"));
|
||||
|
||||
assertEquals("display", respParam.getParameter().get(2).getName());
|
||||
assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
|
||||
assertEquals("display", respParam.getParameter().get(1).getName());
|
||||
assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -810,11 +808,8 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
|
|||
assertEquals("result", respParam.getParameter().get(0).getName());
|
||||
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
|
||||
|
||||
assertEquals("message", respParam.getParameter().get(1).getName());
|
||||
assertThat(((StringType) respParam.getParameter().get(1).getValue()).getValue(), Matchers.containsStringIgnoringCase("succeeded"));
|
||||
|
||||
assertEquals("display", respParam.getParameter().get(2).getName());
|
||||
assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
|
||||
assertEquals("display", respParam.getParameter().get(1).getName());
|
||||
assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
|
|
|
@ -202,10 +202,6 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
|
|||
return names;
|
||||
}
|
||||
|
||||
protected void waitForActivatedSubscriptionCount(int theSize) throws Exception {
|
||||
TestUtil.waitForSize(theSize, () -> mySubscriptionRegistry.size());
|
||||
Thread.sleep(500);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void afterClassClearContextBaseResourceProviderR4Test() throws Exception {
|
||||
|
|
|
@ -10,9 +10,9 @@ import ca.uhn.fhir.jpa.entity.TermValueSet;
|
|||
import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
|
||||
import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation;
|
||||
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.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.ResourceNotFoundException;
|
||||
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_VALUE_SET;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.containsStringIgnoringCase;
|
||||
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.assertNotNull;
|
||||
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.fail;
|
||||
|
||||
|
@ -98,11 +98,11 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
|
|||
|
||||
private void persistCodeSystem(CodeSystem theCodeSystem) {
|
||||
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) {
|
||||
myExtensionalCsId = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
});
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) {
|
||||
myExtensionalCsId = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
});
|
||||
myCodeSystemDao.readEntity(myExtensionalCsId, null).getId();
|
||||
}
|
||||
|
||||
|
@ -156,7 +156,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
|
|||
createLocalVsWithUnknownCode(codeSystem);
|
||||
}
|
||||
|
||||
private void createLocalCsAndVs() {
|
||||
private void createLocalCs() {
|
||||
CodeSystem codeSystem = new CodeSystem();
|
||||
codeSystem.setUrl(URL_MY_CODE_SYSTEM);
|
||||
codeSystem.setContent(CodeSystemContentMode.COMPLETE);
|
||||
|
@ -671,8 +671,8 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
|
|||
.named("$expand")
|
||||
.withNoParameters(Parameters.class)
|
||||
.returnResourceType(ValueSet.class)
|
||||
.execute();
|
||||
ourLog.info("Expanded: {}",myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded));
|
||||
.execute();
|
||||
ourLog.info("Expanded: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded));
|
||||
assertEquals(1, expanded.getExpansion().getContains().size());
|
||||
|
||||
// Update the CodeSystem URL and Codes
|
||||
|
@ -696,12 +696,11 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
|
|||
.withNoParameters(Parameters.class)
|
||||
.returnResourceType(ValueSet.class)
|
||||
.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());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* #516
|
||||
*/
|
||||
|
@ -796,7 +795,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
|
|||
}
|
||||
|
||||
private void validateTermValueSetNotExpanded(String theValueSetName) {
|
||||
runInTransaction(()->{
|
||||
runInTransaction(() -> {
|
||||
Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable);
|
||||
assertTrue(optionalValueSetByResourcePid.isPresent());
|
||||
|
||||
|
@ -814,7 +813,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
|
|||
}
|
||||
|
||||
private void validateTermValueSetExpandedAndChildren(String theValueSetName, CodeSystem theCodeSystem) {
|
||||
runInTransaction(()->{
|
||||
runInTransaction(() -> {
|
||||
Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable);
|
||||
assertTrue(optionalValueSetByResourcePid.isPresent());
|
||||
|
||||
|
@ -906,10 +905,11 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
|
|||
|
||||
@Test
|
||||
public void testValidateCodeOperationByCodeAndSystemInstanceOnType() throws IOException {
|
||||
createLocalCsAndVs();
|
||||
createLocalCs();
|
||||
createLocalVsWithIncludeConcept();
|
||||
|
||||
String url = ourServerBase +
|
||||
"/ValueSet/$validate-code?system=" +
|
||||
"/ValueSet/" + myLocalValueSetId.getIdPart() + "/$validate-code?system=" +
|
||||
UrlUtil.escapeUrlParam(URL_MY_CODE_SYSTEM) +
|
||||
"&code=AA";
|
||||
|
||||
|
@ -926,7 +926,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
|
|||
|
||||
@Test
|
||||
public void testValidateCodeOperationByCodeAndSystemInstanceOnInstance() throws IOException {
|
||||
createLocalCsAndVs();
|
||||
createLocalCs();
|
||||
createLocalVsWithIncludeConcept();
|
||||
|
||||
String url = ourServerBase +
|
||||
|
@ -953,7 +953,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
|
|||
|
||||
Parameters respParam = myClient
|
||||
.operation()
|
||||
.onType(ValueSet.class)
|
||||
.onInstance(myExtensionalVsId)
|
||||
.named("validate-code")
|
||||
.withParameter(Parameters.class, "code", new CodeType("8450-9"))
|
||||
.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());
|
||||
}
|
||||
|
||||
@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
|
||||
public void testValidateCodeAgainstBuiltInSystem() {
|
||||
Parameters respParam = myClient
|
||||
|
@ -983,11 +1001,8 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
|
|||
assertEquals("result", respParam.getParameter().get(0).getName());
|
||||
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
|
||||
|
||||
assertEquals("message", respParam.getParameter().get(1).getName());
|
||||
assertThat(((StringType) respParam.getParameter().get(1).getValue()).getValue(), containsStringIgnoringCase("succeeded"));
|
||||
|
||||
assertEquals("display", respParam.getParameter().get(2).getName());
|
||||
assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
|
||||
assertEquals("display", respParam.getParameter().get(1).getName());
|
||||
assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1018,7 +1033,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
|
|||
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)) {
|
||||
String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info("Response: {}", response);
|
||||
|
@ -1026,7 +1041,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
|
|||
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);
|
||||
|
@ -1070,7 +1085,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
|
|||
TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B");
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -205,23 +205,6 @@ public abstract class BaseResourceProviderR5Test extends BaseJpaR5Test {
|
|||
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
|
||||
public static void afterClassClearContextBaseResourceProviderR5Test() throws Exception {
|
||||
JettyUtil.closeServer(ourServer);
|
||||
|
|
|
@ -10,10 +10,10 @@ import ca.uhn.fhir.jpa.entity.TermValueSet;
|
|||
import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
|
||||
import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation;
|
||||
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.term.api.ITermCodeSystemStorageSvc;
|
||||
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.ResourceNotFoundException;
|
||||
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_VALUE_SET;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.containsStringIgnoringCase;
|
||||
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.assertNotNull;
|
||||
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.fail;
|
||||
|
||||
|
@ -983,8 +983,8 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
|
|||
.named("$expand")
|
||||
.withNoParameters(Parameters.class)
|
||||
.returnResourceType(ValueSet.class)
|
||||
.execute();
|
||||
ourLog.info("Expanded: {}",myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded));
|
||||
.execute();
|
||||
ourLog.info("Expanded: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded));
|
||||
assertEquals(1, expanded.getExpansion().getContains().size());
|
||||
|
||||
// Update the CodeSystem URL and Codes
|
||||
|
@ -1008,12 +1008,11 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
|
|||
.withNoParameters(Parameters.class)
|
||||
.returnResourceType(ValueSet.class)
|
||||
.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());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* #516
|
||||
*/
|
||||
|
@ -1108,7 +1107,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
|
|||
}
|
||||
|
||||
private void validateTermValueSetNotExpanded(String theValueSetName) {
|
||||
runInTransaction(()->{
|
||||
runInTransaction(() -> {
|
||||
Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable);
|
||||
assertTrue(optionalValueSetByResourcePid.isPresent());
|
||||
|
||||
|
@ -1126,7 +1125,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
|
|||
}
|
||||
|
||||
private void validateTermValueSetExpandedAndChildren(String theValueSetName, CodeSystem theCodeSystem) {
|
||||
runInTransaction(()->{
|
||||
runInTransaction(() -> {
|
||||
Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable);
|
||||
assertTrue(optionalValueSetByResourcePid.isPresent());
|
||||
|
||||
|
@ -1219,9 +1218,10 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
|
|||
@Test
|
||||
public void testValidateCodeOperationByCodeAndSystemInstanceOnType() throws IOException {
|
||||
createLocalCsAndVs();
|
||||
createLocalVsWithIncludeConcept();
|
||||
|
||||
String url = ourServerBase +
|
||||
"/ValueSet/$validate-code?system=" +
|
||||
"/ValueSet/" + myLocalValueSetId.getIdPart() + "/$validate-code?system=" +
|
||||
UrlUtil.escapeUrlParam(URL_MY_CODE_SYSTEM) +
|
||||
"&code=AA";
|
||||
|
||||
|
@ -1265,7 +1265,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
|
|||
|
||||
Parameters respParam = myClient
|
||||
.operation()
|
||||
.onType(ValueSet.class)
|
||||
.onInstance(myExtensionalVsId)
|
||||
.named("validate-code")
|
||||
.withParameter(Parameters.class, "code", new CodeType("8450-9"))
|
||||
.andParameter("system", new UriType("http://acme.org"))
|
||||
|
@ -1297,12 +1297,10 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
|
|||
assertEquals("result", respParam.getParameter().get(0).getName());
|
||||
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
|
||||
|
||||
assertEquals("message", respParam.getParameter().get(1).getName());
|
||||
assertThat(((StringType) respParam.getParameter().get(1).getValue()).getValue(), containsStringIgnoringCase("succeeded"));
|
||||
|
||||
assertEquals("display", respParam.getParameter().get(2).getName());
|
||||
assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
|
||||
assertEquals("display", respParam.getParameter().get(1).getName());
|
||||
assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
|
||||
}
|
||||
|
||||
// Good code and system, but not in specified valueset
|
||||
{
|
||||
Parameters respParam = myClient
|
||||
|
@ -1322,7 +1320,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
|
|||
assertEquals(false, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
|
||||
|
||||
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)) {
|
||||
String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
|
||||
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)) {
|
||||
String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info("Response: {}", response);
|
||||
|
@ -1362,7 +1361,32 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
|
|||
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)) {
|
||||
String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info("Response: {}", response);
|
||||
|
@ -1407,7 +1431,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
|
|||
TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B");
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -508,7 +508,7 @@ public class LastNElasticsearchSvcMultipleObservationsIT {
|
|||
for (int entryCount = 0; entryCount < 10; entryCount++) {
|
||||
|
||||
ObservationJson observationJson = new ObservationJson();
|
||||
String identifier = String.valueOf((entryCount + patientCount * 10));
|
||||
String identifier = String.valueOf((entryCount + patientCount * 10L));
|
||||
observationJson.setIdentifier(identifier);
|
||||
observationJson.setSubject(subject);
|
||||
|
||||
|
@ -524,7 +524,7 @@ public class LastNElasticsearchSvcMultipleObservationsIT {
|
|||
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);
|
||||
|
||||
assertTrue(elasticsearchSvc.createOrUpdateObservationIndex(identifier, observationJson));
|
||||
|
|
|
@ -86,4 +86,7 @@ public class SubscriptionTestUtil {
|
|||
subscriber.setEmailSender(myEmailSender);
|
||||
}
|
||||
|
||||
public int getActiveSubscriptionCount() {
|
||||
return mySubscriptionRegistry.size();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -157,6 +157,7 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
|
|||
|
||||
Subscription subscription1 = createSubscription(criteria1, payload, ourListenerServerBase);
|
||||
Subscription subscription2 = createSubscription(criteria2, payload, ourListenerServerBase);
|
||||
waitForActivatedSubscriptionCount(2);
|
||||
|
||||
Observation observation1 = sendObservation(code, "SNOMED-CT");
|
||||
|
||||
|
@ -169,7 +170,6 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
|
|||
ourLog.info("Current interceptors:\n * {}", allInterceptors);
|
||||
|
||||
// Should see 1 subscription notification
|
||||
waitForQueueToDrain();
|
||||
waitForSize(0, ourCreatedObservations);
|
||||
waitForSize(1, ourUpdatedObservations);
|
||||
|
||||
|
@ -181,20 +181,21 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
|
|||
ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute();
|
||||
waitForQueueToDrain();
|
||||
|
||||
ourLog.info("Have {} updates and {} subscriptions - sending observation", ourUpdatedObservations.size(), mySubscriptionTestUtil.getActiveSubscriptionCount());
|
||||
Observation observation2 = sendObservation(code, "SNOMED-CT");
|
||||
waitForQueueToDrain();
|
||||
|
||||
// Should see one subscription notification
|
||||
waitForSize(0, ourCreatedObservations);
|
||||
waitForSize(3, ourUpdatedObservations);
|
||||
|
||||
// Delet one subscription
|
||||
// Delete one subscription
|
||||
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");
|
||||
|
||||
// Should see only one subscription notification
|
||||
waitForQueueToDrain();
|
||||
waitForSize(0, ourCreatedObservations);
|
||||
waitForSize(4, ourUpdatedObservations);
|
||||
|
||||
|
@ -204,6 +205,7 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
|
|||
CodingDt coding = codeableConcept.addCoding();
|
||||
coding.setCode(code + "111");
|
||||
coding.setSystem("SNOMED-CT");
|
||||
ourLog.info("Have {} updates and {} subscriptions - sending observation", ourUpdatedObservations.size(), mySubscriptionTestUtil.getActiveSubscriptionCount());
|
||||
ourClient.update().resource(observation3).withId(observation3.getIdElement()).execute();
|
||||
|
||||
// Should see no subscription notification
|
||||
|
@ -218,6 +220,7 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
|
|||
CodingDt coding1 = codeableConcept1.addCoding();
|
||||
coding1.setCode(code);
|
||||
coding1.setSystem("SNOMED-CT");
|
||||
ourLog.info("Have {} updates and {} subscriptions - sending observation", ourUpdatedObservations.size(), mySubscriptionTestUtil.getActiveSubscriptionCount());
|
||||
ourClient.update().resource(observation3a).withId(observation3a.getIdElement()).execute();
|
||||
|
||||
// Should see only one subscription notification
|
||||
|
|
|
@ -7,6 +7,7 @@ import ca.uhn.fhir.rest.api.Constants;
|
|||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
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.IIdType;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
|
@ -250,7 +251,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
|
|||
|
||||
// Manually unregister all subscriptions
|
||||
mySubscriptionRegistry.unregisterAllSubscriptions();
|
||||
waitForActivatedSubscriptionCount(0);
|
||||
assertEquals(0, mySubscriptionRegistry.size());
|
||||
|
||||
// Force a reload
|
||||
mySubscriptionLoader.doSyncSubscriptionsForUnitTest();
|
||||
|
|
|
@ -21,9 +21,7 @@ import ca.uhn.fhir.rest.annotation.Update;
|
|||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
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.util.TestUtil;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
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.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* Test the rest-hook subscriptions
|
||||
|
@ -90,23 +87,6 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B
|
|||
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 {
|
||||
Subscription subscription = new Subscription();
|
||||
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);
|
||||
|
||||
|
||||
runInTransaction(()->{
|
||||
ourLog.info("All token indexes:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
|
||||
runInTransaction(() -> {
|
||||
ourLog.info("All token indexes:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
|
||||
});
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
|
|
|
@ -2,10 +2,8 @@ package ca.uhn.fhir.jpa.term;
|
|||
|
||||
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||
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.term.api.ITermLoaderSvc;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.hl7.fhir.dstu3.model.CodeType;
|
||||
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.StringType;
|
||||
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.instance.model.api.IPrimitiveType;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -30,12 +28,12 @@ import java.util.Set;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
|
||||
|
@ -228,11 +226,13 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
|
|||
ZipCollectionBuilder files = new ZipCollectionBuilder();
|
||||
TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files);
|
||||
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());
|
||||
assertEquals("Found code", result.getMessage());
|
||||
assertTrue(result.isOk());
|
||||
assertEquals("R' wave amplitude in lead I", result.getDisplay());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -240,11 +240,13 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
|
|||
ZipCollectionBuilder files = new ZipCollectionBuilder();
|
||||
TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files);
|
||||
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());
|
||||
assertEquals("Code not found", result.getMessage());
|
||||
assertFalse(result.isOk());
|
||||
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) {
|
||||
|
|
|
@ -3,7 +3,6 @@ package ca.uhn.fhir.jpa.term;
|
|||
import ca.uhn.fhir.context.support.ConceptValidationOptions;
|
||||
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||
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.entity.TermConceptMap;
|
||||
import ca.uhn.fhir.jpa.entity.TermConceptMapGroup;
|
||||
|
@ -43,8 +42,8 @@ import static org.junit.jupiter.api.Assertions.fail;
|
|||
|
||||
public class TerminologySvcImplR4Test extends BaseTermR4Test {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(TerminologySvcImplR4Test.class);
|
||||
ValidationOptions optsNoGuess = new ValidationOptions();
|
||||
ValidationOptions optsGuess = new ValidationOptions().guessSystem();
|
||||
ConceptValidationOptions optsNoGuess = new ConceptValidationOptions();
|
||||
ConceptValidationOptions optsGuess = new ConceptValidationOptions().setInferSystem(true);
|
||||
private IIdType myConceptMapId;
|
||||
|
||||
private void createAndPersistConceptMap() {
|
||||
|
@ -1799,42 +1798,37 @@ public class TerminologySvcImplR4Test extends BaseTermR4Test {
|
|||
|
||||
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);
|
||||
|
||||
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);
|
||||
assertNull(result);
|
||||
assertFalse(result.isOk());
|
||||
|
||||
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", null, null, null);
|
||||
assertTrue(result.isResult());
|
||||
assertEquals("Validation succeeded", result.getMessage());
|
||||
assertTrue(result.isOk());
|
||||
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);
|
||||
assertTrue(result.isResult());
|
||||
assertEquals("Validation succeeded", result.getMessage());
|
||||
assertTrue(result.isOk());
|
||||
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
|
||||
|
||||
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, "http://acme.org", "11378-7", null, null, null);
|
||||
assertTrue(result.isResult());
|
||||
assertEquals("Validation succeeded", result.getMessage());
|
||||
assertTrue(result.isOk());
|
||||
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
|
||||
|
||||
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);
|
||||
assertTrue(result.isResult());
|
||||
assertEquals("Validation succeeded", result.getMessage());
|
||||
assertTrue(result.isOk());
|
||||
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
|
||||
|
||||
CodeableConcept codeableConcept = new CodeableConcept();
|
||||
codeableConcept.addCoding(new Coding("BOGUS", "BOGUS", "BOGUS"));
|
||||
codeableConcept.addCoding(coding);
|
||||
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, codeableConcept);
|
||||
assertTrue(result.isResult());
|
||||
assertEquals("Validation succeeded", result.getMessage());
|
||||
assertTrue(result.isOk());
|
||||
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
|
||||
}
|
||||
|
||||
|
@ -1852,43 +1846,38 @@ public class TerminologySvcImplR4Test extends BaseTermR4Test {
|
|||
|
||||
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);
|
||||
|
||||
|
||||
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);
|
||||
assertNull(result);
|
||||
assertFalse(result.isOk());
|
||||
|
||||
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", null, null, null);
|
||||
assertTrue(result.isResult());
|
||||
assertEquals("Validation succeeded", result.getMessage());
|
||||
assertTrue(result.isOk());
|
||||
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);
|
||||
assertTrue(result.isResult());
|
||||
assertEquals("Validation succeeded", result.getMessage());
|
||||
assertTrue(result.isOk());
|
||||
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
|
||||
|
||||
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, "http://acme.org", "11378-7", null, null, null);
|
||||
assertTrue(result.isResult());
|
||||
assertEquals("Validation succeeded", result.getMessage());
|
||||
assertTrue(result.isOk());
|
||||
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
|
||||
|
||||
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);
|
||||
assertTrue(result.isResult());
|
||||
assertEquals("Validation succeeded", result.getMessage());
|
||||
assertTrue(result.isOk());
|
||||
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
|
||||
|
||||
CodeableConcept codeableConcept = new CodeableConcept();
|
||||
codeableConcept.addCoding(new Coding("BOGUS", "BOGUS", "BOGUS"));
|
||||
codeableConcept.addCoding(coding);
|
||||
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, codeableConcept);
|
||||
assertTrue(result.isResult());
|
||||
assertEquals("Validation succeeded", result.getMessage());
|
||||
assertTrue(result.isOk());
|
||||
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -26,6 +26,10 @@
|
|||
<code value="8450-9" />
|
||||
<display value="Systolic blood pressure--expiration" />
|
||||
</concept>
|
||||
<concept>
|
||||
<code value="11378-7" />
|
||||
<display value="Systolic blood pressure at First encounter" />
|
||||
</concept>
|
||||
</codeSystem>
|
||||
<compose>
|
||||
<include>
|
||||
|
@ -124,4 +128,4 @@
|
|||
</concept>
|
||||
</include>
|
||||
</compose>
|
||||
</ValueSet>
|
||||
</ValueSet>
|
||||
|
|
|
@ -74,7 +74,11 @@ public class EmpiResourceDaoSvc {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -35,6 +35,7 @@ public abstract class BaseMigrator implements IMigrator {
|
|||
private boolean myDryRun;
|
||||
private boolean myNoColumnShrink;
|
||||
private boolean myOutOfOrderPermitted;
|
||||
private boolean mySchemaWasInitialized;
|
||||
private DriverTypeEnum myDriverType;
|
||||
private DataSource myDataSource;
|
||||
private List<BaseTask.ExecutedStatement> myExecutedStatements = new ArrayList<>();
|
||||
|
@ -111,4 +112,13 @@ public abstract class BaseMigrator implements IMigrator {
|
|||
}
|
||||
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
Loading…
Reference in New Issue