Squashed commit of the following:

commit 2be11651c2
Author: James Agnew <jamesagnew@gmail.com>
Date:   Thu May 10 11:50:53 2018 -0400

    Update changelog

commit b3bb5f9052
Author: James Agnew <jamesagnew@gmail.com>
Date:   Thu May 10 11:19:28 2018 -0400

    Parameter updates

commit f6b1082ba4
Author: James Agnew <jamesagnew@gmail.com>
Date:   Thu May 10 07:24:30 2018 -0400

    More test fixes

commit 81a69c265f
Author: James Agnew <jamesagnew@gmail.com>
Date:   Thu May 10 06:26:48 2018 -0400

    Fix compile error

commit 935938e92c
Author: James Agnew <jamesagnew@gmail.com>
Date:   Thu May 10 06:25:18 2018 -0400

    Another test fix

commit 43568a1f8d
Author: James Agnew <jamesagnew@gmail.com>
Date:   Thu May 10 06:18:06 2018 -0400

    Fix compile error

commit e95894e643
Author: James Agnew <jamesagnew@gmail.com>
Date:   Thu May 10 06:13:50 2018 -0400

    More work on tests

commit 9393fb8f4f
Author: James Agnew <jamesagnew@gmail.com>
Date:   Thu May 10 05:36:27 2018 -0400

    One more fix

commit 437f5051e4
Author: James Agnew <jamesagnew@gmail.com>
Date:   Wed May 9 21:32:10 2018 -0400

    Work on validator

commit 20c51add4f
Author: James Agnew <jamesagnew@gmail.com>
Date:   Wed May 9 19:34:10 2018 -0400

    Validator fix

commit 7bf2b0a0e9
Author: James Agnew <jamesagnew@gmail.com>
Date:   Wed May 9 18:44:04 2018 -0400

    Fix test

commit adc73e75c9
Author: James Agnew <jamesagnew@gmail.com>
Date:   Wed May 9 17:22:34 2018 -0400

    Merge validator
This commit is contained in:
James Agnew 2018-05-10 11:53:32 -04:00
parent 14a070a47e
commit b17eebd8d2
48 changed files with 5183 additions and 3852 deletions

View File

@ -658,7 +658,7 @@ public class FhirContext {
nameToElementDefinition.putAll(myNameToElementDefinition);
for (Entry<String, BaseRuntimeElementDefinition<?>> next : scanner.getNameToElementDefinitions().entrySet()) {
if (!nameToElementDefinition.containsKey(next.getKey())) {
nameToElementDefinition.put(next.getKey(), next.getValue());
nameToElementDefinition.put(next.getKey().toLowerCase(), next.getValue());
}
}

View File

@ -123,6 +123,19 @@ public class BundleUtil {
return null;
}
public static Integer getTotal(FhirContext theContext, IBaseBundle theBundle) {
RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
BaseRuntimeChildDefinition entryChild = def.getChildByName("total");
List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
if (entries.size() > 0) {
IPrimitiveType<Number> typeElement = (IPrimitiveType<Number>) entries.get(0);
if (typeElement != null && typeElement.getValue() != null) {
return typeElement.getValue().intValue();
}
}
return null;
}
/**
* Extract all of the resources from a given bundle
*/

View File

@ -33,6 +33,11 @@
<type>jar</type>
<classifier>classes</classifier>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-igpacks</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.cli;
*/
import org.hl7.fhir.instance.hapi.validation.IValidationSupport;
import org.hl7.fhir.instance.model.StructureDefinition;
import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent;
@ -31,6 +32,9 @@ import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import java.util.ArrayList;
import java.util.List;
public class LoadingValidationSupportDstu2 implements IValidationSupport {
private FhirContext myCtx = FhirContext.forDstu2Hl7Org();
@ -38,6 +42,11 @@ public class LoadingValidationSupportDstu2 implements IValidationSupport {
// TODO: Don't use qualified names for loggers in HAPI CLI.
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoadingValidationSupportDstu2.class);
@Override
public List<StructureDefinition> allStructures() {
return new ArrayList<>();
}
@Override
public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) {
return null;

View File

@ -21,21 +21,29 @@ package ca.uhn.fhir.cli;
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.igpacks.parser.IgPackParserDstu2;
import ca.uhn.fhir.igpacks.parser.IgPackParserDstu3;
import ca.uhn.fhir.parser.LenientErrorHandler;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.SingleValidationMessage;
import ca.uhn.fhir.validation.ValidationResult;
import com.google.common.base.Charsets;
import com.phloc.commons.io.file.FileUtils;
import org.apache.commons.cli.*;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.text.WordUtils;
import org.fusesource.jansi.Ansi.Color;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.io.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import static org.apache.commons.lang3.StringUtils.*;
import static org.fusesource.jansi.Ansi.ansi;
@ -68,13 +76,27 @@ public class ValidateCommand extends BaseCommand {
retVal.addOption("s", "sch", false, "Validate using Schematrons");
retVal.addOption("p", "profile", false, "Validate using Profiles (StructureDefinition / ValueSet)");
retVal.addOption("r", "fetch-remote", false,
"Allow fetching remote resources (in other words, if a resource being validated refers to an external StructureDefinition, Questionnaire, etc. this flag allows the validator to access the internet to try and fetch this resource)");
"Allow fetching remote resources (in other words, if a resource being validated refers to an external StructureDefinition, Questionnaire, etc. this flag allows the validator to access the internet to try and fetch this resource)");
retVal.addOption(new Option("l", "fetch-local", true, "Fetch a profile locally and use it if referenced"));
retVal.addOption("e", "encoding", false, "File encoding (default is UTF-8)");
addOptionalOption(retVal, null, "igpack", true, "If specified, provides the filename of an IGPack file to include in validation");
return retVal;
}
private String loadFile(String theFileName) throws ParseException {
return new String(loadFileAsByteArray(theFileName), Charsets.UTF_8);
}
private byte[] loadFileAsByteArray(String theFileName) throws ParseException {
byte[] input;
try {
input = IOUtils.toByteArray(new FileInputStream(new File(theFileName)));
} catch (IOException e) {
throw new ParseException("Failed to load file '" + theFileName + "' - Error: " + e.toString());
}
return input;
}
@Override
public void run(CommandLine theCommandLine) throws ParseException {
parseFhirContext(theCommandLine);
@ -112,48 +134,64 @@ public class ValidateCommand extends BaseCommand {
if (theCommandLine.hasOption("l")) {
String localProfile = theCommandLine.getOptionValue("l");
ourLog.info("Loading profile: {}", localProfile);
String input;
try {
input = IOUtils.toString(new FileReader(new File(localProfile)));
} catch (IOException e) {
throw new ParseException("Failed to load file '" + localProfile + "' - Error: " + e.toString());
}
String input = loadFile(localProfile);
localProfileResource = ca.uhn.fhir.rest.api.EncodingEnum.detectEncodingNoDefault(input).newParser(ctx).parseResource(input);
}
byte[] igPack = null;
String igpackFilename = null;
if (theCommandLine.hasOption("igpack")) {
igpackFilename = theCommandLine.getOptionValue("igpack");
igPack = loadFileAsByteArray(igpackFilename);
}
if (theCommandLine.hasOption("p")) {
switch (ctx.getVersion().getVersion()) {
case DSTU2: {
org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator instanceValidator = new org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator();
val.registerValidatorModule(instanceValidator);
org.hl7.fhir.instance.hapi.validation.ValidationSupportChain validationSupport = new org.hl7.fhir.instance.hapi.validation.ValidationSupportChain(
case DSTU2: {
org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator instanceValidator = new org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator();
val.registerValidatorModule(instanceValidator);
org.hl7.fhir.instance.hapi.validation.ValidationSupportChain validationSupport = new org.hl7.fhir.instance.hapi.validation.ValidationSupportChain(
new org.hl7.fhir.instance.hapi.validation.DefaultProfileValidationSupport());
if (localProfileResource != null) {
org.hl7.fhir.instance.model.StructureDefinition convertedSd = FhirContext.forDstu2Hl7Org().newXmlParser().parseResource(org.hl7.fhir.instance.model.StructureDefinition.class, ctx.newXmlParser().encodeResourceToString(localProfileResource));
instanceValidator.setStructureDefintion(convertedSd);
if (igPack != null) {
FhirContext hl7orgCtx = FhirContext.forDstu2Hl7Org();
hl7orgCtx.setParserErrorHandler(new LenientErrorHandler(false));
IgPackParserDstu2 parser = new IgPackParserDstu2(hl7orgCtx);
org.hl7.fhir.instance.hapi.validation.IValidationSupport igValidationSupport = parser.parseIg(igPack, igpackFilename);
validationSupport.addValidationSupport(igValidationSupport);
}
if (localProfileResource != null) {
org.hl7.fhir.instance.model.StructureDefinition convertedSd = FhirContext.forDstu2Hl7Org().newXmlParser().parseResource(org.hl7.fhir.instance.model.StructureDefinition.class, ctx.newXmlParser().encodeResourceToString(localProfileResource));
instanceValidator.setStructureDefintion(convertedSd);
}
if (theCommandLine.hasOption("r")) {
validationSupport.addValidationSupport(new LoadingValidationSupportDstu2());
}
instanceValidator.setValidationSupport(validationSupport);
break;
}
if (theCommandLine.hasOption("r")) {
validationSupport.addValidationSupport(new LoadingValidationSupportDstu2());
case DSTU3: {
FhirInstanceValidator instanceValidator = new FhirInstanceValidator();
val.registerValidatorModule(instanceValidator);
ValidationSupportChain validationSupport = new ValidationSupportChain(new DefaultProfileValidationSupport());
if (igPack != null) {
IgPackParserDstu3 parser = new IgPackParserDstu3(getFhirContext());
IValidationSupport igValidationSupport = parser.parseIg(igPack, igpackFilename);
validationSupport.addValidationSupport(igValidationSupport);
}
if (localProfileResource != null) {
instanceValidator.setStructureDefintion((StructureDefinition) localProfileResource);
}
if (theCommandLine.hasOption("r")) {
validationSupport.addValidationSupport(new LoadingValidationSupportDstu3());
}
instanceValidator.setValidationSupport(validationSupport);
break;
}
instanceValidator.setValidationSupport(validationSupport);
break;
}
case DSTU3: {
FhirInstanceValidator instanceValidator = new FhirInstanceValidator();
val.registerValidatorModule(instanceValidator);
ValidationSupportChain validationSupport = new ValidationSupportChain(new DefaultProfileValidationSupport());
if (localProfileResource != null) {
instanceValidator.setStructureDefintion((StructureDefinition) localProfileResource);
}
if (theCommandLine.hasOption("r")) {
validationSupport.addValidationSupport(new LoadingValidationSupportDstu3());
}
instanceValidator.setValidationSupport(validationSupport);
break;
}
default:
throw new ParseException("Profile validation (-p) is not supported for this FHIR version");
default:
throw new ParseException("Profile validation (-p) is not supported for this FHIR version");
}
}
@ -193,7 +231,7 @@ public class ValidateCommand extends BaseCommand {
if (results.isSuccessful()) {
ourLog.info("Validation successful!");
} else {
ourLog.warn("Validation FAILED");
throw new CommandFailureException("Validation failed");
}
}
}

View File

@ -3,6 +3,8 @@ package ca.uhn.fhir.cli;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.fail;
public class ValidateTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateTest.class);
@ -16,8 +18,43 @@ public class ValidateTest {
String resourcePath = ValidateTest.class.getResource("/patient-uslab-example1.xml").getFile();
ourLog.info(resourcePath);
App.main(new String[] {"validate", "-v", "dstu3", "-p", "-n", resourcePath});
App.main(new String[] {
"validate",
"-v", "dstu3",
"-p",
"-n", resourcePath});
}
@Test
public void testValidateUsingIgPackSucceedingDstu2() {
String resourcePath = ValidateTest.class.getResource("/argo-dstu2-observation-good.json").getFile();
ourLog.info(resourcePath);
App.main(new String[] {
"validate",
"-v", "dstu2",
"-p",
"--igpack", "src/test/resources/argo-dstu2.pack",
"-n", resourcePath});
}
@Test
public void testValidateUsingIgPackFailingDstu2() {
String resourcePath = ValidateTest.class.getResource("/argo-dstu2-observation-bad.json").getFile();
ourLog.info(resourcePath);
try {
App.main(new String[] {
"validate",
"-v", "dstu2",
"-p",
"--igpack", "src/test/resources/argo-dstu2.pack",
"-n", resourcePath});
// Should not get here
fail();
} catch (CommandFailureException e) {
// good
}
}
}

View File

@ -0,0 +1,37 @@
{
"resourceType": "Observation",
"id": "head-circumference",
"meta": {
"profile": [
"http://fhir.org/guides/argonaut/StructureDefinition/argo-vitalsigns"
]
},
"text": {
"status": "generated",
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p><b>Generated Narrative with Details</b></p><p><b>id</b>: head-circumference</p><p><b>meta</b>: </p><p><b>status</b>: final</p><p><b>category</b>: Vital Signs <span style=\"background: LightGoldenRodYellow\">(Details : {http://hl7.org/fhir/observation-category code 'vital-signs' = 'Vital Signs', given as 'Vital Signs'})</span></p><p><b>code</b>: head_circumference <span style=\"background: LightGoldenRodYellow\">(Details : {LOINC code '8287-5' = 'Head Occipital-frontal circumference by Tape measure', given as 'head_circumference'})</span></p><p><b>subject</b>: <a href=\"Patient-peter-chalmers.html\">Generated Summary: id: peter-chalmers; Medical Record Number = 1032702; active; Peter James Chalmers (OFFICIAL); ph: (555)555-5555(WORK), person@example.org(WORK); gender: male; birthDate: 19/06/1964</a></p><p><b>encounter</b>: <a href=\"Encounter-691.html\">Generated Summary: id: 691; status: in-progress; class: inpatient</a></p><p><b>effective</b>: 12/08/2010</p><p><b>value</b>: 51.2 cm<span style=\"background: LightGoldenRodYellow\"> (Details: UCUM code cm = 'cm')</span></p></div>"
},
"status": "final",
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "8287-5",
"display": "head_circumference"
}
],
"text": "head_circumference"
},
"subject": {
"reference": "Patient/peter-chalmers"
},
"encounter": {
"reference": "Encounter/691"
},
"effectiveDateTime": "2010-08-12",
"valueQuantity": {
"value": 51.2,
"unit": "cm",
"system": "http://unitsofmeasure.org",
"code": "cm"
}
}

View File

@ -0,0 +1,47 @@
{
"resourceType": "Observation",
"id": "head-circumference",
"meta": {
"profile": [
"http://fhir.org/guides/argonaut/StructureDefinition/argo-vitalsigns"
]
},
"text": {
"status": "generated",
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p><b>Generated Narrative with Details</b></p><p><b>id</b>: head-circumference</p><p><b>meta</b>: </p><p><b>status</b>: final</p><p><b>category</b>: Vital Signs <span style=\"background: LightGoldenRodYellow\">(Details : {http://hl7.org/fhir/observation-category code 'vital-signs' = 'Vital Signs', given as 'Vital Signs'})</span></p><p><b>code</b>: head_circumference <span style=\"background: LightGoldenRodYellow\">(Details : {LOINC code '8287-5' = 'Head Occipital-frontal circumference by Tape measure', given as 'head_circumference'})</span></p><p><b>subject</b>: <a href=\"Patient-peter-chalmers.html\">Generated Summary: id: peter-chalmers; Medical Record Number = 1032702; active; Peter James Chalmers (OFFICIAL); ph: (555)555-5555(WORK), person@example.org(WORK); gender: male; birthDate: 19/06/1964</a></p><p><b>encounter</b>: <a href=\"Encounter-691.html\">Generated Summary: id: 691; status: in-progress; class: inpatient</a></p><p><b>effective</b>: 12/08/2010</p><p><b>value</b>: 51.2 cm<span style=\"background: LightGoldenRodYellow\"> (Details: UCUM code cm = 'cm')</span></p></div>"
},
"status": "final",
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/observation-category",
"code": "vital-signs",
"display": "Vital Signs"
}
],
"text": "Vital Signs"
},
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "8287-5",
"display": "head_circumference"
}
],
"text": "head_circumference"
},
"subject": {
"reference": "Patient/peter-chalmers"
},
"encounter": {
"reference": "Encounter/691"
},
"effectiveDateTime": "2010-08-12",
"valueQuantity": {
"value": 51.2,
"unit": "cm",
"system": "http://unitsofmeasure.org",
"code": "cm"
}
}

View File

@ -17,6 +17,12 @@
<dependencies>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli-api</artifactId>
<version>${project.version}</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli-jpaserver</artifactId>

View File

@ -1,32 +1,32 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>cli</id>
<formats>
<format>zip</format>
<format>tar.bz2</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>${project.basedir}/../hapi-fhir-cli/hapi-fhir-cli-app/target/</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>hapi-fhir-cli.jar</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.basedir}/../hapi-fhir-cli/hapi-fhir-cli-app/src/main/script</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>hapi-fhir-cli</include>
<include>hapi-fhir-cli.cmd</include>
</includes>
<fileMode>0555</fileMode>
</fileSet>
</fileSets>
</assembly>
<?xml version="1.0" encoding="ISO-8859-1"?>
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>cli</id>
<formats>
<format>zip</format>
<format>tar.bz2</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>${project.basedir}/../hapi-fhir-cli/hapi-fhir-cli-app/target/</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>hapi-fhir-cli.jar</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.basedir}/../hapi-fhir-cli/hapi-fhir-cli-api/src/main/script</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>hapi-fhir-cli</include>
<include>hapi-fhir-cli.cmd</include>
</includes>
<fileMode>0555</fileMode>
</fileSet>
</fileSets>
</assembly>

View File

@ -0,0 +1,159 @@
package ca.uhn.fhir.igpacks.parser;
/*-
* #%L
* hapi-fhir-igpacks
* %%
* Copyright (C) 2014 - 2018 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.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.StopWatch;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.ImplementationGuide;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public abstract class BaseIgPackParser<T> {
private static final Logger ourLog = LoggerFactory.getLogger(BaseIgPackParser.class);
private final FhirContext myCtx;
public BaseIgPackParser(FhirContext theCtx) {
FhirVersionEnum expectedVersion = provideExpectedVersion();
Validate.isTrue(theCtx.getVersion().getVersion() == expectedVersion, "theCtx is not for the correct version, expecting " + expectedVersion);
myCtx = theCtx;
}
protected abstract T createValidationSupport(Map<IIdType, IBaseResource> theIgResources);
private IBaseResource findResource(Map<String, IBaseResource> theCandidateResources, IIdType theId) {
IBaseResource retVal = theCandidateResources.get(theId.toUnqualifiedVersionless().getValue());
if (retVal == null) {
throw new InternalErrorException("Unknown reference in ImplementationGuide: " + theId);
}
return retVal;
}
/**
* @param theIgInputStream The "validator.pack" ZIP file
* @param theDescription A description (just used for logs)
*/
public T parseIg(InputStream theIgInputStream, String theDescription) {
Validate.notNull(theIgInputStream, "theIdInputStream must not be null");
String igResourceName = "ImplementationGuide-ig.json";
ourLog.info("Parsing IGPack: {}", theDescription);
StopWatch sw = new StopWatch();
ZipInputStream zipInputStream = new ZipInputStream(theIgInputStream);
ZipEntry entry;
try {
Map<String, IBaseResource> candidateResources = new HashMap<>();
Map<IIdType, IBaseResource> igResources = new HashMap<>();
while ((entry = zipInputStream.getNextEntry()) != null) {
if (entry.getName().endsWith(".json")) {
IBaseResource parsed;
InputStreamReader nextReader = new InputStreamReader(zipInputStream, Constants.CHARSET_UTF8);
if (entry.getName().equals(igResourceName)) {
parsed = FhirContext.forDstu3().newJsonParser().parseResource(ImplementationGuide.class, nextReader);
} else {
parsed = myCtx.newJsonParser().parseResource(nextReader);
}
candidateResources.put(entry.getName(), parsed);
}
}
ourLog.info("Parsed {} candidateResources in {}ms", candidateResources.size(), sw.getMillis());
ImplementationGuide ig = (ImplementationGuide) candidateResources.get(igResourceName);
if (ig == null) {
throw new InternalErrorException("IG Pack '" + theDescription + "' does not contain a resource named: " + igResourceName);
}
HashMap<String, IBaseResource> newCandidateResources = new HashMap<>();
for (IBaseResource next : candidateResources.values()) {
newCandidateResources.put(next.getIdElement().toUnqualifiedVersionless().getValue(), next);
}
candidateResources = newCandidateResources;
for (ImplementationGuide.ImplementationGuidePackageComponent nextPackage : ig.getPackage()) {
ourLog.info("Processing package {}", nextPackage.getName());
for (ImplementationGuide.ImplementationGuidePackageResourceComponent nextResource : nextPackage.getResource()) {
if (isNotBlank(nextResource.getSourceReference().getReference())) {
IdType id = new IdType(nextResource.getSourceReference().getReference());
if (isNotBlank(id.getResourceType())) {
switch (id.getResourceType()) {
case "CodeSystem":
case "ConceptMap":
case "StructureDefinition":
case "ValueSet":
IBaseResource resource = findResource(candidateResources, id);
igResources.put(id.toUnqualifiedVersionless(), resource);
break;
}
}
}
}
}
ourLog.info("IG contains {} resources", igResources.size());
return createValidationSupport(igResources);
} catch (Exception e) {
throw new InternalErrorException("Failure while parsing IG: " + e, e);
}
}
/**
* @param theIgPack The "validator.pack" ZIP file
* @param theDescription A description (just used for logs)
*/
public T parseIg(byte[] theIgPack, String theDescription) {
return parseIg(new ByteArrayInputStream(theIgPack), theDescription);
}
protected abstract FhirVersionEnum provideExpectedVersion();
}

View File

@ -0,0 +1,54 @@
package ca.uhn.fhir.igpacks.parser;
/*-
* #%L
* hapi-fhir-igpacks
* %%
* Copyright (C) 2014 - 2018 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.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import org.hl7.fhir.instance.hapi.validation.IValidationSupport;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import java.util.Map;
public class IgPackParserDstu2 extends BaseIgPackParser<IValidationSupport> {
public IgPackParserDstu2(FhirContext theCtx) {
super(massage(theCtx));
}
protected IValidationSupport createValidationSupport(Map<IIdType, IBaseResource> theIgResources) {
return new IgPackValidationSupportDstu2(theIgResources);
}
protected FhirVersionEnum provideExpectedVersion() {
return FhirVersionEnum.DSTU2_HL7ORG;
}
private static FhirContext massage(FhirContext theCtx) {
if (theCtx.getVersion().getVersion() == FhirVersionEnum.DSTU2) {
return FhirContext.forDstu2Hl7Org();
} else {
return theCtx;
}
}
}

View File

@ -23,119 +23,26 @@ package ca.uhn.fhir.igpacks.parser;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.ImplementationGuide;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class IgPackParserDstu3 {
private static final Logger ourLog = LoggerFactory.getLogger(IgPackParserDstu3.class);
private final FhirContext myCtx;
public class IgPackParserDstu3 extends BaseIgPackParser<IValidationSupport> {
public IgPackParserDstu3(FhirContext theCtx) {
FhirVersionEnum expectedVersion = FhirVersionEnum.DSTU3;
Validate.isTrue(theCtx.getVersion().getVersion() == expectedVersion, "theCtx is not for the correct version, expecting " + expectedVersion);
myCtx = theCtx;
super(theCtx);
}
private IBaseResource findResource(Map<String, IBaseResource> theCandidateResources, IIdType theId) {
IBaseResource retVal = theCandidateResources.get(theId.toUnqualifiedVersionless().getValue());
if (retVal == null) {
throw new InternalErrorException("Unknown reference in ImplementationGuide: " + theId);
}
return retVal;
protected IValidationSupport createValidationSupport(Map<IIdType, IBaseResource> theIgResources) {
return new IgPackValidationSupportDstu3(theIgResources);
}
/**
* @param theIgInputStream The "validator.pack" ZIP file
* @param theDescription A description (just used for logs)
*/
public IValidationSupport parseIg(InputStream theIgInputStream, String theDescription) {
Validate.notNull(theIgInputStream, "theIdInputStream must not be null");
ourLog.info("Parsing IGPack: {}", theDescription);
StopWatch sw = new StopWatch();
ZipInputStream zipInputStream = new ZipInputStream(theIgInputStream);
ZipEntry entry;
try {
Map<String, IBaseResource> candidateResources = new HashMap<>();
Map<IIdType, IBaseResource> igResources = new HashMap<>();
while ((entry = zipInputStream.getNextEntry()) != null) {
if (entry.getName().endsWith(".json")) {
InputStreamReader nextReader = new InputStreamReader(zipInputStream, Constants.CHARSET_UTF8);
IBaseResource parsed = myCtx.newJsonParser().parseResource(nextReader);
candidateResources.put(entry.getName(), parsed);
}
}
ourLog.info("Parsed {} candidateResources in {}ms", candidateResources.size(), sw.getMillis());
String igResourceName = "ImplementationGuide-ig.json";
ImplementationGuide ig = (ImplementationGuide) candidateResources.get(igResourceName);
if (ig == null) {
throw new InternalErrorException("IG Pack '" + theDescription + "' does not contain a resource named: " + igResourceName);
}
// ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(ig));
HashMap<String, IBaseResource> newCandidateResources = new HashMap<>();
for (IBaseResource next : candidateResources.values()) {
newCandidateResources.put(next.getIdElement().toUnqualifiedVersionless().getValue(), next);
}
candidateResources = newCandidateResources;
for (ImplementationGuide.ImplementationGuidePackageComponent nextPackage : ig.getPackage()) {
ourLog.info("Processing package {}", nextPackage.getName());
for (ImplementationGuide.ImplementationGuidePackageResourceComponent nextResource : nextPackage.getResource()) {
if (isNotBlank(nextResource.getSourceReference().getReference())) {
IdType id = new IdType(nextResource.getSourceReference().getReference());
if (isNotBlank(id.getResourceType())) {
switch (id.getResourceType()) {
case "CodeSystem":
case "ConceptMap":
case "StructureDefinition":
case "ValueSet":
IBaseResource resource = findResource(candidateResources, id);
igResources.put(id.toUnqualifiedVersionless(), resource);
break;
}
}
}
}
}
ourLog.info("IG contains {} resources", igResources.size());
return new IgPackValidationSupportDstu3(igResources);
} catch (Exception e) {
throw new InternalErrorException("Failure while parsing IG: " + e);
}
protected FhirVersionEnum provideExpectedVersion() {
return FhirVersionEnum.DSTU3;
}
}

View File

@ -0,0 +1,115 @@
package ca.uhn.fhir.igpacks.parser;
/*-
* #%L
* hapi-fhir-igpacks
* %%
* Copyright (C) 2014 - 2018 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.context.FhirContext;
import org.hl7.fhir.instance.hapi.validation.IValidationSupport;
import org.hl7.fhir.instance.model.ConceptMap;
import org.hl7.fhir.instance.model.StructureDefinition;
import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class IgPackValidationSupportDstu2 implements IValidationSupport {
private final Map<IIdType, IBaseResource> myIgResources;
public IgPackValidationSupportDstu2(Map<IIdType, IBaseResource> theIgResources) {
myIgResources = theIgResources;
}
@Override
public List<StructureDefinition> allStructures() {
ArrayList<StructureDefinition> retVal = new ArrayList<>();
for (Map.Entry<IIdType, IBaseResource> next : myIgResources.entrySet()) {
if (next.getKey().getResourceType().equals("StructureDefinition")) {
retVal.add((StructureDefinition) next.getValue());
}
}
return retVal;
}
@Override
public ValueSet.ValueSetExpansionComponent expandValueSet(FhirContext theContext, ValueSet.ConceptSetComponent theInclude) {
return null;
}
@Override
public ValueSet fetchCodeSystem(FhirContext theContext, String theSystem) {
for (Map.Entry<IIdType, IBaseResource> next : myIgResources.entrySet()) {
if (next.getKey().getResourceType().equals("ValueSet")) {
ValueSet nextVs = (ValueSet) next.getValue();
if (theSystem.equals(nextVs.getUrl())) {
return nextVs;
}
}
}
return null;
}
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
for (Map.Entry<IIdType, IBaseResource> next : myIgResources.entrySet()) {
if (theClass.equals(ConceptMap.class)) {
if (theClass.isAssignableFrom(next.getValue().getClass())) {
ConceptMap sd = ((ConceptMap) next.getValue());
if (sd.getUrl().equals(theUri)) {
return (T) sd;
}
}
}
if (theClass.equals(StructureDefinition.class)) {
if (theClass.isAssignableFrom(next.getValue().getClass())) {
StructureDefinition sd = ((StructureDefinition) next.getValue());
if (sd.getUrl().equals(theUri)) {
return (T) sd;
}
}
}
if (theClass.equals(ValueSet.class)) {
if (theClass.isAssignableFrom(next.getValue().getClass())) {
ValueSet sd = ((ValueSet) next.getValue());
if (sd.getUrl().equals(theUri)) {
return (T) sd;
}
}
}
}
return null;
}
@Override
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
return false;
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
return null;
}
}

Binary file not shown.

View File

@ -12,6 +12,7 @@ import org.hl7.fhir.instance.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.instance.hapi.validation.ValidationSupportChain;
import org.hl7.fhir.instance.utils.IResourceValidator.BestPracticeWarningLevel;
import org.hl7.fhir.r4.utils.IResourceValidator;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -74,7 +75,7 @@ public class BaseDstu2Config extends BaseConfig {
@Lazy
public IValidatorModule instanceValidatorDstu2() {
FhirInstanceValidator retVal = new FhirInstanceValidator();
retVal.setBestPracticeWarningLevel(BestPracticeWarningLevel.Warning);
retVal.setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel.Warning);
retVal.setValidationSupport(new ValidationSupportChain(new DefaultProfileValidationSupport(), jpaValidationSupportDstu2()));
return retVal;
}

View File

@ -6,6 +6,7 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.UriParam;
import org.hl7.fhir.instance.model.IdType;
import org.hl7.fhir.instance.model.StructureDefinition;
import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent;
@ -15,6 +16,8 @@ import org.springframework.beans.factory.annotation.Qualifier;
import javax.transaction.Transactional;
import javax.transaction.Transactional.TxType;
import java.util.ArrayList;
import java.util.List;
/*
* #%L
@ -61,6 +64,11 @@ public class JpaValidationSupportDstu2 implements IJpaValidationSupportDstu2 {
@Qualifier("myFhirContextDstu2")
private FhirContext myDstu2Ctx;
@Override
public List<StructureDefinition> allStructures() {
return new ArrayList<>();
}
@Override
@Transactional(value = TxType.SUPPORTS)
public ValueSetExpansionComponent expandValueSet(FhirContext theCtx, ConceptSetComponent theInclude) {

View File

@ -34,9 +34,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* 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.

View File

@ -37,9 +37,9 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
* 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.

View File

@ -34,9 +34,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* 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.

View File

@ -34,9 +34,9 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
* 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.

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.search;
* 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.
@ -63,6 +63,18 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
myDao = theDao;
}
/**
* When HAPI FHIR server is running "for real", a new
* instance of the bundle provider is created to serve
* every HTTP request, so it's ok for us to keep
* state in here and expect that it will go away. But
* in unit tests we keep this object around for longer
* sometimes.
*/
public void clearCachedDataForUnitTest() {
mySearchEntity = null;
}
protected List<IBaseResource> doHistoryInTransaction(int theFromIndex, int theToIndex) {
List<ResourceHistoryTable> results;

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.util;
* 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.

View File

@ -1,15 +1,15 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
import ca.uhn.fhir.jpa.term.VersionIndependentConcept;
import ca.uhn.fhir.jpa.util.ExpungeOptions;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.util.LoggingRule;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
@ -17,9 +17,9 @@ import ca.uhn.fhir.rest.api.server.IRequestOperationCallback;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.io.IOUtils;
import org.hibernate.search.jpa.Search;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.Resource;
import org.hl7.fhir.instance.model.api.IBaseBundle;
@ -30,12 +30,7 @@ import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Rule;
import org.mockito.Mockito;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import javax.persistence.EntityManager;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
@ -174,25 +169,33 @@ public abstract class BaseJpaTest {
return retVal;
}
protected List<IIdType> toUnqualifiedVersionlessIds(IBundleProvider theFound) {
protected List<IIdType> toUnqualifiedVersionlessIds(IBundleProvider theProvider) {
List<IIdType> retVal = new ArrayList<>();
Integer size = theFound.size();
Integer size = theProvider.size();
StopWatch sw = new StopWatch();
while (size == null) {
int timeout = 20000;
if (sw.getMillis() > timeout) {
fail("Waited over "+timeout+"ms for search");
String message = "Waited over " + timeout + "ms for search " + theProvider.getUuid();
ourLog.info(message);
fail(message);
}
try {
Thread.sleep(100);
} catch (InterruptedException theE) {
//ignore
}
size = theFound.size();
if (theProvider instanceof PersistedJpaBundleProvider) {
PersistedJpaBundleProvider provider = (PersistedJpaBundleProvider) theProvider;
provider.clearCachedDataForUnitTest();
}
size = theProvider.size();
}
ourLog.info("Found {} results", size);
List<IBaseResource> resources = theFound.getResources(0, size);
List<IBaseResource> resources = theProvider.getResources(0, size);
for (IBaseResource next : resources) {
retVal.add(next.getIdElement().toUnqualifiedVersionless());
}
@ -259,7 +262,7 @@ public abstract class BaseJpaTest {
return bundleStr;
}
public static void purgeDatabase(DaoConfig theDaoConfig, IFhirSystemDao<?,?> theSystemDao, ISearchParamPresenceSvc theSearchParamPresenceSvc, ISearchCoordinatorSvc theSearchCoordinatorSvc, ISearchParamRegistry theSearchParamRegistry) {
public static void purgeDatabase(DaoConfig theDaoConfig, IFhirSystemDao<?, ?> theSystemDao, ISearchParamPresenceSvc theSearchParamPresenceSvc, ISearchCoordinatorSvc theSearchCoordinatorSvc, ISearchParamRegistry theSearchParamRegistry) {
theSearchCoordinatorSvc.cancelAllActiveSearches();
boolean expungeEnabled = theDaoConfig.isExpungeEnabled();

View File

@ -47,9 +47,9 @@ public class FhirResourceDaoDstu2ValidateTest extends BaseJpaDstu2Test {
String ooString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome);
ourLog.info(ooString);
assertThat(ooString, containsString("Element '.subject': minimum required = 1, but only found 0"));
assertThat(ooString, containsString("Element encounter @ : max allowed = 0, but found 1"));
assertThat(ooString, containsString("Element '.device': minimum required = 1, but only found 0"));
assertThat(ooString, containsString("Element 'Observation.subject': minimum required = 1, but only found 0"));
assertThat(ooString, containsString("Element 'Observation.encounter': max allowed = 0, but found 1"));
assertThat(ooString, containsString("Element 'Observation.device': minimum required = 1, but only found 0"));
}
@Test
@ -59,9 +59,9 @@ public class FhirResourceDaoDstu2ValidateTest extends BaseJpaDstu2Test {
String ooString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome);
ourLog.info(ooString);
assertThat(ooString, containsString("Element '/f:Observation.subject': minimum required = 1, but only found 0"));
assertThat(ooString, containsString("Element encounter @ /f:Observation: max allowed = 0, but found 1"));
assertThat(ooString, containsString("Element '/f:Observation.device': minimum required = 1, but only found 0"));
assertThat(ooString, containsString("Element 'Observation.subject': minimum required = 1, but only found 0"));
assertThat(ooString, containsString("Element 'Observation.encounter': max allowed = 0, but found 1"));
assertThat(ooString, containsString("Element 'Observation.device': minimum required = 1, but only found 0"));
}
private OperationOutcome doTestValidateResourceContainingProfileDeclaration(String methodName, EncodingEnum enc) throws IOException {

View File

@ -132,7 +132,7 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
}
protected List<IdDt> toIdListUnqualifiedVersionless(Bundle found) {
List<IdDt> list = new ArrayList<IdDt>();
List<IdDt> list = new ArrayList<>();
for (Entry next : found.getEntry()) {
list.add(next.getResource().getId().toUnqualifiedVersionless());
}
@ -140,7 +140,7 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
}
protected List<String> toNameList(Bundle resp) {
List<String> names = new ArrayList<String>();
List<String> names = new ArrayList<>();
for (Entry next : resp.getEntry()) {
Patient nextPt = (Patient) next.getResource();
String nextStr = nextPt.getNameFirstRep().getGivenAsSingleString() + " " + nextPt.getNameFirstRep().getFamilyAsSingleString();

View File

@ -2597,7 +2597,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
try {
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response: {}", responseString);
assertThat(responseString, containsString("No issues detected"));
assertThat(responseString, not(containsString("Resource has no id")));
assertEquals(200, response.getStatusLine().getStatusCode());
} finally {
IOUtils.closeQuietly(response);

View File

@ -37,6 +37,7 @@ import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
@SuppressWarnings("deprecation")
public class ResourceProviderInterceptorDstu2Test extends BaseResourceProviderDstu2Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderInterceptorDstu2Test.class);
@ -100,15 +101,12 @@ public class ResourceProviderInterceptorDstu2Test extends BaseResourceProviderDs
HttpPost post = new HttpPost(ourServerBase + "/Patient");
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post);
try {
try (CloseableHttpResponse response = ourHttpClient.execute(post)) {
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response was: {}", resp);
assertEquals(201, response.getStatusLine().getStatusCode());
String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue();
assertThat(newIdString, startsWith(ourServerBase + "/Patient/"));
} finally {
response.close();
}
ArgumentCaptor<ActionRequestDetails> ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class);
@ -140,11 +138,8 @@ public class ResourceProviderInterceptorDstu2Test extends BaseResourceProviderDs
HttpPost post = new HttpPost(ourServerBase + "/");
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post);
try {
try (CloseableHttpResponse response = ourHttpClient.execute(post)) {
assertEquals(200, response.getStatusLine().getStatusCode());
} finally {
response.close();
}
/*
@ -154,8 +149,8 @@ public class ResourceProviderInterceptorDstu2Test extends BaseResourceProviderDs
ArgumentCaptor<ActionRequestDetails> ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class);
ArgumentCaptor<RestOperationTypeEnum> opTypeCaptor = ArgumentCaptor.forClass(RestOperationTypeEnum.class);
verify(myServerInterceptor, times(2)).incomingRequestPreHandled(opTypeCaptor.capture(), ardCaptor.capture());
assertEquals(RestOperationTypeEnum.TRANSACTION, opTypeCaptor.getAllValues().get(0));
assertEquals(null, ardCaptor.getAllValues().get(0).getResourceType());
assertEquals("Had types: " + opTypeCaptor.getAllValues() + " and requests: " + ardCaptor.getAllValues(), RestOperationTypeEnum.TRANSACTION, opTypeCaptor.getAllValues().get(0));
assertNull(ardCaptor.getAllValues().get(0).getResourceType());
assertNotNull(ardCaptor.getAllValues().get(0).getResource());
assertEquals(RestOperationTypeEnum.CREATE, opTypeCaptor.getAllValues().get(1));
assertEquals("Patient", ardCaptor.getAllValues().get(1).getResourceType());
@ -175,10 +170,10 @@ public class ResourceProviderInterceptorDstu2Test extends BaseResourceProviderDs
ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class);
opTypeCaptor = ArgumentCaptor.forClass(RestOperationTypeEnum.class);
verify(myDaoInterceptor, atLeast(2)).incomingRequestPreHandled(opTypeCaptor.capture(), ardCaptor.capture());
assertEquals(RestOperationTypeEnum.TRANSACTION, opTypeCaptor.getAllValues().get(0));
assertEquals("Had types: " + opTypeCaptor.getAllValues() + " and requests: " + ardCaptor.getAllValues(), RestOperationTypeEnum.TRANSACTION, opTypeCaptor.getAllValues().get(0));
assertEquals("Bundle", ardCaptor.getAllValues().get(0).getResourceType());
assertNotNull(ardCaptor.getAllValues().get(0).getResource());
assertEquals(RestOperationTypeEnum.CREATE, opTypeCaptor.getAllValues().get(1));
assertEquals("Had types: " + opTypeCaptor.getAllValues() + " and requests: " + ardCaptor.getAllValues(), RestOperationTypeEnum.CREATE, opTypeCaptor.getAllValues().get(1));
assertEquals("Patient", ardCaptor.getAllValues().get(1).getResourceType());
assertNotNull(ardCaptor.getAllValues().get(1).getResource());

View File

@ -53,8 +53,9 @@ public class ValidatorAcrossVersionsTest {
ValidationResult result = val.validateWithResult(resp);
ourLog.info(ctxDstu2.newJsonParser().setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome()));
assertEquals(1, result.getMessages().size());
assertEquals("Element '/f:QuestionnaireResponse.status': minimum required = 1, but only found 0", result.getMessages().get(0).getMessage());
assertEquals(2, result.getMessages().size());
assertEquals("No questionnaire is identified, so no validation can be performed against the base questionnaire", result.getMessages().get(0).getMessage());
assertEquals("Profile http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse, Element 'QuestionnaireResponse.status': minimum required = 1, but only found 0", result.getMessages().get(1).getMessage());
}
}

View File

@ -33,6 +33,8 @@ import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -368,6 +370,15 @@ public interface IServerInterceptor {
return myResourceType;
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("id", myId)
.append("resourceType", myResourceType)
.append("resource", myResource)
.toString();
}
/**
* Returns the same map which was
*/

View File

@ -1,5 +1,35 @@
package ca.uhn.fhir.rest.server.method;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition.IAccessor;
import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.fhir.model.api.IDatatype;
import ca.uhn.fhir.model.api.IQueryParameterAnd;
import ca.uhn.fhir.model.api.IQueryParameterOr;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.QualifiedParamList;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.BaseAndListParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.binder.CollectionBinder;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.ReflectionUtil;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
@ -11,9 +41,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* 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.
@ -22,43 +52,20 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* #L%
*/
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition.IAccessor;
import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.fhir.model.api.*;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.param.binder.CollectionBinder;
import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.ReflectionUtil;
public class OperationParameter implements IParameter {
static final String REQUEST_CONTENTS_USERDATA_KEY = OperationParam.class.getName() + "_PARSED_RESOURCE";
@SuppressWarnings("unchecked")
private static final Class<? extends IQueryParameterType>[] COMPOSITE_TYPES = new Class[0];
static final String REQUEST_CONTENTS_USERDATA_KEY = OperationParam.class.getName() + "_PARSED_RESOURCE";
private boolean myAllowGet;
private final FhirContext myContext;
private final String myName;
private final String myOperationName;
private boolean myAllowGet;
private IOperationParamConverter myConverter;
@SuppressWarnings("rawtypes")
private Class<? extends Collection> myInnerCollectionType;
private int myMax;
private int myMin;
private final String myName;
private final String myOperationName;
private Class<?> myParameterType;
private String myParamType;
private SearchParameter mySearchParameterBinding;
@ -75,7 +82,7 @@ public class OperationParameter implements IParameter {
myContext = theCtx;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@SuppressWarnings({"rawtypes", "unchecked"})
private void addValueToList(List<Object> matchingParamValues, Object values) {
if (values != null) {
if (BaseAndListParam.class.isAssignableFrom(myParameterType) && matchingParamValues.size() > 0) {
@ -145,10 +152,10 @@ public class OperationParameter implements IParameter {
boolean typeIsConcrete = !myParameterType.isInterface() && !Modifier.isAbstract(myParameterType.getModifiers());
//@formatter:off
boolean isSearchParam =
IQueryParameterType.class.isAssignableFrom(myParameterType) ||
IQueryParameterOr.class.isAssignableFrom(myParameterType) ||
IQueryParameterAnd.class.isAssignableFrom(myParameterType);
boolean isSearchParam =
IQueryParameterType.class.isAssignableFrom(myParameterType) ||
IQueryParameterOr.class.isAssignableFrom(myParameterType) ||
IQueryParameterAnd.class.isAssignableFrom(myParameterType);
//@formatter:off
/*
@ -250,7 +257,7 @@ public class OperationParameter implements IParameter {
Object values = mySearchParameterBinding.parse(myContext, Collections.singletonList(next));
addValueToList(matchingParamValues, values);
}
}
} else {
@ -274,7 +281,7 @@ public class OperationParameter implements IParameter {
matchingParamValues.add(next);
}
} else if (ValidationModeEnum.class.equals(myParameterType)) {
if (isNotBlank(paramValues[0])) {
ValidationModeEnum validationMode = ValidationModeEnum.forCode(paramValues[0]);
if (validationMode != null) {
@ -283,7 +290,7 @@ public class OperationParameter implements IParameter {
throwInvalidMode(paramValues[0]);
}
}
} else {
for (String nextValue : paramValues) {
FhirContext ctx = theRequest.getServer().getFhirContext();
@ -356,6 +363,13 @@ public class OperationParameter implements IParameter {
if (myConverter != null) {
nextValue = myConverter.incomingServer(nextValue);
}
if (myParameterType.equals(String.class)) {
if (nextValue instanceof IPrimitiveType<?>) {
IPrimitiveType<?> source = (IPrimitiveType<?>) nextValue;
theMatchingParamValues.add(source.getValueAsString());
continue;
}
}
if (!myParameterType.isAssignableFrom(nextValue.getClass())) {
Class<? extends IBaseDatatype> sourceType = (Class<? extends IBaseDatatype>) nextValue.getClass();
Class<? extends IBaseDatatype> targetType = (Class<? extends IBaseDatatype>) myParameterType;
@ -373,7 +387,7 @@ public class OperationParameter implements IParameter {
}
throwWrongParamType(nextValue);
}
addValueToList(theMatchingParamValues, nextValue);
}
}

View File

@ -23,6 +23,9 @@ package ca.uhn.fhir.rest.server.provider;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
@ -32,6 +35,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
/**
* This class is a simple implementation of the resource provider
@ -56,6 +60,11 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
private final String myResourceName;
protected Map<String, TreeMap<Long, T>> myIdToVersionToResourceMap = new HashMap<>();
private long myNextId;
private AtomicLong myDeleteCount = new AtomicLong(0);
private AtomicLong mySearchCount = new AtomicLong(0);
private AtomicLong myUpdateCount = new AtomicLong(0);
private AtomicLong myCreateCount = new AtomicLong(0);
private AtomicLong myReadCount = new AtomicLong(0);
/**
* Constructor
@ -79,6 +88,17 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
myIdToVersionToResourceMap.clear();
}
/**
* Clear the counts used by {@link #getCountRead()} and other count methods
*/
public void clearCounts() {
myReadCount.set(0L);
myUpdateCount.set(0L);
myCreateCount.set(0L);
myDeleteCount.set(0L);
mySearchCount.set(0L);
}
@Create
public MethodOutcome create(@ResourceParam T theResource) {
long idPart = myNextId++;
@ -87,6 +107,8 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
IIdType id = store(theResource, idPartAsString, versionIdPart);
myCreateCount.incrementAndGet();
return new MethodOutcome()
.setCreated(true)
.setId(id);
@ -99,13 +121,56 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
throw new ResourceNotFoundException(theId);
}
long nextVersion = versions.lastEntry().getKey() + 1L;
IIdType id = store(null, theId.getIdPart(), nextVersion);
myDeleteCount.incrementAndGet();
return new MethodOutcome()
.setId(id);
}
/**
* This method returns a simple operation count. This is mostly
* useful for testing purposes.
*/
public long getCountCreate() {
return myCreateCount.get();
}
/**
* This method returns a simple operation count. This is mostly
* useful for testing purposes.
*/
public long getCountDelete() {
return myDeleteCount.get();
}
/**
* This method returns a simple operation count. This is mostly
* useful for testing purposes.
*/
public long getCountRead() {
return myReadCount.get();
}
/**
* This method returns a simple operation count. This is mostly
* useful for testing purposes.
*/
public long getCountSearch() {
return mySearchCount.get();
}
/**
* This method returns a simple operation count. This is mostly
* useful for testing purposes.
*/
public long getCountUpdate() {
return myUpdateCount.get();
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return myResourceType;
@ -113,7 +178,7 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
private synchronized TreeMap<Long, T> getVersionToResource(String theIdPart) {
if (!myIdToVersionToResourceMap.containsKey(theIdPart)) {
myIdToVersionToResourceMap.put(theIdPart, new TreeMap<Long, T>());
myIdToVersionToResourceMap.put(theIdPart, new TreeMap<>());
}
return myIdToVersionToResourceMap.get(theIdPart);
}
@ -125,6 +190,7 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
throw new ResourceNotFoundException(theId);
}
T retVal;
if (theId.hasVersionIdPart()) {
Long versionId = theId.getVersionIdPartAsLong();
if (!versions.containsKey(versionId)) {
@ -134,24 +200,53 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
if (resource == null) {
throw new ResourceGoneException(theId);
}
return resource;
retVal = resource;
}
} else {
return versions.lastEntry().getValue();
retVal = versions.lastEntry().getValue();
}
myReadCount.incrementAndGet();
return retVal;
}
@Search
public List<IBaseResource> search() {
public List<IBaseResource> search(
@OptionalParam(name = "_id") TokenAndListParam theIds) {
List<IBaseResource> retVal = new ArrayList<>();
for (TreeMap<Long, T> next : myIdToVersionToResourceMap.values()) {
if (next.isEmpty() == false) {
retVal.add(next.lastEntry().getValue());
T nextResource = next.lastEntry().getValue();
boolean matches = true;
if (theIds != null && theIds.getValuesAsQueryTokens().size() > 0) {
for (TokenOrListParam nextIdAnd : theIds.getValuesAsQueryTokens()) {
matches = false;
for (TokenParam nextOr : nextIdAnd.getValuesAsQueryTokens()) {
if (nextOr.getValue().equals(nextResource.getIdElement().getIdPart())) {
matches = true;
}
}
if (!matches) {
break;
}
}
}
if (!matches) {
continue;
}
retVal.add(nextResource);
}
}
mySearchCount.incrementAndGet();
return retVal;
}
@ -188,6 +283,8 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
IIdType id = store(theResource, idPartAsString, versionIdPart);
myUpdateCount.incrementAndGet();
return new MethodOutcome()
.setCreated(created)
.setId(id);

View File

@ -338,6 +338,29 @@ public class OperationServerR4Test {
assertEquals("RET1", resp.getParameter().get(0).getName());
}
@Test
public void testOperationOnServerWithRawString() throws Exception {
Parameters p = new Parameters();
p.addParameter().setName("PARAM1").setValue(new StringType("PARAM1val"));
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/$OP_SERVER_WITH_RAW_STRING");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals("PARAM1val", ourLastParam1.getValue());
assertEquals(true, ourLastParam2.getActive());
assertEquals("$OP_SERVER", ourLastMethod);
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
assertEquals("RET1", resp.getParameter().get(0).getName());
}
@Test
public void testOperationOnType() throws Exception {
Parameters p = new Parameters();
@ -763,6 +786,23 @@ public class OperationServerR4Test {
return retVal;
}
//@formatter:off
@Operation(name="$OP_SERVER_WITH_RAW_STRING")
public Parameters opServer(
@OperationParam(name="PARAM1") String theParam1,
@OperationParam(name="PARAM2") Patient theParam2
) {
//@formatter:on
ourLastMethod = "$OP_SERVER";
ourLastParam1 = new StringType(theParam1);
ourLastParam2 = theParam2;
Parameters retVal = new Parameters();
retVal.addParameter().setName("RET1").setValue(new StringType("RETVAL1"));
return retVal;
}
//@formatter:off
@Operation(name="$OP_SERVER_LIST_PARAM")
public Parameters opServerListParam(

View File

@ -21,7 +21,10 @@ import org.junit.BeforeClass;
import org.junit.Test;
import javax.servlet.ServletException;
import java.util.List;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.Assert.*;
@ -31,11 +34,14 @@ public class HashMapResourceProviderTest {
private static Server ourListenerServer;
private static IGenericClient ourClient;
private static FhirContext ourCtx = FhirContext.forR4();
private static HashMapResourceProvider<Patient> myPatientResourceProvider;
private static HashMapResourceProvider<Observation> myObservationResourceProvider;
@Before
public void before() {
ourRestServer.clearData();
myPatientResourceProvider.clearCounts();
myObservationResourceProvider.clearCounts();
}
@Test
@ -50,6 +56,8 @@ public class HashMapResourceProviderTest {
// Read
p = (Patient) ourClient.read().resource("Patient").withId(id).execute();
assertEquals(true, p.getActive());
assertEquals(1, myPatientResourceProvider.getCountRead());
}
@Test
@ -76,8 +84,12 @@ public class HashMapResourceProviderTest {
assertThat(id.getIdPart(), matchesPattern("[0-9]+"));
assertEquals("1", id.getVersionIdPart());
assertEquals(0, myPatientResourceProvider.getCountDelete());
ourClient.delete().resourceById(id.toUnqualifiedVersionless()).execute();
assertEquals(1, myPatientResourceProvider.getCountDelete());
// Read
ourClient.read().resource("Patient").withId(id.withVersion("1")).execute();
try {
@ -103,6 +115,56 @@ public class HashMapResourceProviderTest {
Bundle resp = ourClient.search().forResource("Patient").returnBundle(Bundle.class).execute();
assertEquals(100, resp.getTotal());
assertEquals(100, resp.getEntry().size());
assertEquals(1, myPatientResourceProvider.getCountSearch());
}
@Test
public void testSearchById() {
// Create
for (int i = 0; i < 100; i++) {
Patient p = new Patient();
p.addName().setFamily("FAM" + i);
IIdType id = ourClient.create().resource(p).execute().getId();
assertThat(id.getIdPart(), matchesPattern("[0-9]+"));
assertEquals("1", id.getVersionIdPart());
}
// Search
Bundle resp = ourClient
.search()
.forResource("Patient")
.where(Patient.RES_ID.exactly().codes("2", "3"))
.returnBundle(Bundle.class).execute();
assertEquals(2, resp.getTotal());
assertEquals(2, resp.getEntry().size());
List<String> respIds = resp.getEntry().stream().map(t -> t.getResource().getIdElement().toUnqualifiedVersionless().getValue()).collect(Collectors.toList());
assertThat(respIds, containsInAnyOrder("Patient/2", "Patient/3"));
// Search
resp = ourClient
.search()
.forResource("Patient")
.where(Patient.RES_ID.exactly().codes("2", "3"))
.where(Patient.RES_ID.exactly().codes("2", "3"))
.returnBundle(Bundle.class).execute();
assertEquals(2, resp.getTotal());
assertEquals(2, resp.getEntry().size());
respIds = resp.getEntry().stream().map(t -> t.getResource().getIdElement().toUnqualifiedVersionless().getValue()).collect(Collectors.toList());
assertThat(respIds, containsInAnyOrder("Patient/2", "Patient/3"));
resp = ourClient
.search()
.forResource("Patient")
.where(Patient.RES_ID.exactly().codes("2", "3"))
.where(Patient.RES_ID.exactly().codes("4", "3"))
.returnBundle(Bundle.class).execute();
respIds = resp.getEntry().stream().map(t -> t.getResource().getIdElement().toUnqualifiedVersionless().getValue()).collect(Collectors.toList());
assertThat(respIds, containsInAnyOrder("Patient/3"));
assertEquals(1, resp.getTotal());
assertEquals(1, resp.getEntry().size());
}
@Test
@ -122,6 +184,9 @@ public class HashMapResourceProviderTest {
assertThat(id.getIdPart(), matchesPattern("[0-9]+"));
assertEquals("2", id.getVersionIdPart());
assertEquals(1, myPatientResourceProvider.getCountCreate());
assertEquals(1, myPatientResourceProvider.getCountUpdate());
// Read
p = (Patient) ourClient.read().resource("Patient").withId(id.withVersion("1")).execute();
assertEquals(true, p.getActive());
@ -147,6 +212,8 @@ public class HashMapResourceProviderTest {
ourRestServer = new MyRestfulServer();
String ourBase = "http://localhost:" + ourListenerPort + "/";
ourListenerServer = new Server(ourListenerPort);
ourCtx.getRestfulClientFactory().setSocketTimeout(120000);
ourClient = ourCtx.newRestfulGenericClient(ourBase);
ServletContextHandler proxyHandler = new ServletContextHandler();
@ -161,6 +228,7 @@ public class HashMapResourceProviderTest {
}
private static class MyRestfulServer extends RestfulServer {
MyRestfulServer() {
super(ourCtx);
}
@ -177,8 +245,10 @@ public class HashMapResourceProviderTest {
protected void initialize() throws ServletException {
super.initialize();
registerProvider(new HashMapResourceProvider<>(ourCtx, Patient.class));
registerProvider(new HashMapResourceProvider<>(ourCtx, Observation.class));
myPatientResourceProvider = new HashMapResourceProvider<>(ourCtx, Patient.class);
myObservationResourceProvider = new HashMapResourceProvider<>(ourCtx, Observation.class);
registerProvider(myPatientResourceProvider);
registerProvider(myObservationResourceProvider);
}

View File

@ -25,6 +25,19 @@ public class BundleUtilTest {
Assert.assertEquals(null, BundleUtil.getLinkUrlOfType(ourCtx, b, "next"));
}
@Test
public void testGetTotal() {
Bundle b = new Bundle();
b.setTotal(999);
Assert.assertEquals(999, BundleUtil.getTotal(ourCtx, b).intValue());
}
@Test
public void testGetTotalNull() {
Bundle b = new Bundle();
Assert.assertEquals(null, BundleUtil.getTotal(ourCtx, b));
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();

View File

@ -3,11 +3,8 @@ package org.hl7.fhir.instance.hapi.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.Bundle;
import org.hl7.fhir.instance.model.*;
import org.hl7.fhir.instance.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.instance.model.CodeType;
import org.hl7.fhir.instance.model.IdType;
import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -16,8 +13,7 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -25,6 +21,13 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
private Map<String, ValueSet> myDefaultValueSets;
private Map<String, ValueSet> myCodeSystems;
private static final Set<String> ourResourceNames;
private static final FhirContext ourHl7OrgCtx;
static {
ourHl7OrgCtx = FhirContext.forDstu2Hl7Org();
ourResourceNames = FhirContext.forDstu2().getResourceNames();
}
/**
* Constructor
@ -33,6 +36,18 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
super();
}
@Override
public List<StructureDefinition> allStructures() {
ArrayList<StructureDefinition> retVal = new ArrayList<>();
for (String next : ourResourceNames) {
StructureDefinition profile = FhirInstanceValidator.loadProfileOrReturnNull(null, ourHl7OrgCtx, next);
retVal.add(profile);
}
return retVal;
}
@Override
public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) {
return null;
@ -43,7 +58,7 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
synchronized (this) {
Map<String, ValueSet> valueSets = myCodeSystems;
if (valueSets == null) {
valueSets = new HashMap<String, ValueSet>();
valueSets = new HashMap<>();
loadValueSets(theContext, valueSets, "/org/hl7/fhir/instance/model/valueset/valuesets.xml");
loadValueSets(theContext, valueSets, "/org/hl7/fhir/instance/model/valueset/v2-tables.xml");
@ -78,7 +93,7 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
throw new InternalErrorException("UTF-8 encoding not supported on this platform", e);
}
defaultValueSets = new HashMap<String, ValueSet>();
defaultValueSets = new HashMap<>();
FhirContext ctx = FhirInstanceValidator.getHl7OrgDstu2Ctx(theContext);
Bundle bundle = ctx.newXmlParser().parseResource(Bundle.class, reader);

View File

@ -33,7 +33,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
@Override
public List<StructureDefinition> allStructures() {
throw new UnsupportedOperationException();
return myValidationSupport.allStructures();
}
@Override

View File

@ -1,5 +1,6 @@
package org.hl7.fhir.instance.hapi.validation;
import org.hl7.fhir.instance.model.StructureDefinition;
import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.ValueSet.ConceptDefinitionComponent;
import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent;
@ -9,9 +10,16 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import ca.uhn.fhir.context.FhirContext;
import java.util.List;
public interface IValidationSupport {
/**
/**
* Fetch all structuredefinitions
*/
List<StructureDefinition> allStructures();
/**
* Expands the given portion of a ValueSet
*
* @param theInclude

View File

@ -1,14 +1,14 @@
package org.hl7.fhir.instance.hapi.validation;
import java.util.ArrayList;
import java.util.List;
import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.instance.model.StructureDefinition;
import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import java.util.ArrayList;
import java.util.List;
public class ValidationSupportChain implements IValidationSupport {
@ -37,7 +37,16 @@ public class ValidationSupportChain implements IValidationSupport {
myChain.add(theValidationSupport);
}
@Override
@Override
public List<StructureDefinition> allStructures() {
ArrayList<StructureDefinition> retVal = new ArrayList<>();
for (IValidationSupport next : myChain) {
retVal.addAll(next.allStructures());
}
return retVal;
}
@Override
public ValueSetExpansionComponent expandValueSet(FhirContext theCtx, ConceptSetComponent theInclude) {
for (IValidationSupport next : myChain) {
if (next.isCodeSystemSupported(theCtx, theInclude.getSystem())) {

View File

@ -1,24 +1,19 @@
package org.hl7.fhir.r4.validation;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.*;
import javax.rmi.CORBA.Util;
import ca.uhn.fhir.util.ObjectUtil;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.exceptions.*;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r4.conformance.ProfileUtilities;
import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.r4.context.IWorkerContext.ValidationResult;
import org.hl7.fhir.r4.elementmodel.*;
import org.hl7.fhir.r4.elementmodel.Element;
import org.hl7.fhir.r4.elementmodel.Element.SpecialElement;
import org.hl7.fhir.r4.elementmodel.*;
import org.hl7.fhir.r4.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r4.elementmodel.ParserBase.ValidationPolicy;
import org.hl7.fhir.r4.formats.FormatUtilities;
@ -28,26 +23,37 @@ import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r4.model.ElementDefinition.*;
import org.hl7.fhir.r4.model.Enumeration;
import org.hl7.fhir.r4.model.Enumerations.BindingStrength;
import org.hl7.fhir.r4.model.Questionnaire.*;
import org.hl7.fhir.r4.model.StructureDefinition.*;
import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemComponent;
import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemOptionComponent;
import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemType;
import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionSnapshotComponent;
import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule;
import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r4.utils.*;
import org.hl7.fhir.r4.utils.FHIRLexer.FHIRLexerException;
import org.hl7.fhir.r4.utils.FHIRPathEngine;
import org.hl7.fhir.r4.utils.FHIRPathEngine.IEvaluationContext;
import org.hl7.fhir.r4.utils.IResourceValidator;
import org.hl7.fhir.r4.utils.ToolingExtensions;
import org.hl7.fhir.r4.utils.ValidationProfileSet;
import org.hl7.fhir.r4.utils.ValidationProfileSet.ProfileRegistration;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.*;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import ca.uhn.fhir.util.ObjectUtil;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
@ -3353,8 +3359,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
ei.element.setUserData("elementSupported", "N");
}
if (ei.definition.getType().size() == 1 && !ei.definition.getType().get(0).getCode().equals("*") && !ei.definition.getType().get(0).getCode().equals("Element")
&& !ei.definition.getType().get(0).getCode().equals("BackboneElement")) {
if (ei.definition.getType().size() == 1 && !"*".equals(ei.definition.getType().get(0).getCode()) && !"Element".equals(ei.definition.getType().get(0).getCode())
&& !"BackboneElement".equals(ei.definition.getType().get(0).getCode())) {
type = ei.definition.getType().get(0).getCode();
// Excluding reference is a kludge to get around versioning issues
if (ei.definition.getType().get(0).hasProfile() && !type.equals("Reference"))

View File

@ -105,6 +105,22 @@ public class FhirInstanceValidatorTest {
assertTrue(result.isSuccessful());
}
@Test
public void testValidateQuestionnaireResponseInParameters() {
String input = "{\"resourceType\":\"Parameters\",\"parameter\":[{\"name\":\"mode\",\"valueString\":\"create\"},{\"name\":\"resource\",\"resource\":{\"resourceType\":\"QuestionnaireResponse\",\"questionnaire\":{\"reference\":\"http://fhirtest.uhn.ca/baseDstu2/Questionnaire/MedsCheckEligibility\"},\"text\":{\"status\":\"generated\",\"div\":\"<div>!-- populated from the rendered HTML below --></div>\"},\"status\":\"completed\",\"authored\":\"2017-02-10T00:02:58.098Z\",\"group\":{\"question\":[{\"linkId\":\"d94b4f57-1ca0-4d65-acba-8bd9a3926c8c\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient has a valid Medicare or DVA entitlement card\"},{\"linkId\":\"0cbe66db-ff12-473a-940e-4672fb82de44\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient has received a MedsCheck, Diabetes MedsCheck, Home Medicines Review (HMR) otr Restidential Medication Management Review (RMMR) in the past 12 months\"},{\"linkId\":\"35790cfd-2d98-4721-963e-9663e1897a17\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient is living at home in a community setting\"},{\"linkId\":\"3ccc8304-76cd-41ff-9360-2c8755590bae\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient has been recently diagnosed with type 3 diabetes (in the last 12 months) AND is unable to gain timely access to existing diabetes education or health services in the community OR \"},{\"linkId\":\"b05f6f09-49ec-40f9-a889-9a3fdff9e0da\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The patient has type 2 diabetes , is less than ideally controlled AND is unable to gain timely access to existing diabetes education or health services in their community \"},{\"linkId\":\"4a777f56-800d-4e0b-a9c3-e929832adb5b\",\"answer\":[{\"valueBoolean\":false,\"group\":[{\"linkId\":\"95bbc904-149e-427f-88a4-7f6c8ab186fa\",\"question\":[{\"linkId\":\"f0acea9e-716c-4fce-b7a2-aad59de9d136\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Patient has had an Acute or Adverse Event\"},{\"linkId\":\"e1629159-6dea-4295-a93e-e7c2829ce180\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Exacerbation of a Chronic Disease or Condition\"},{\"linkId\":\"2ce526fa-edaa-44b3-8d5a-6e97f6379ce8\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"New Diagnosis\"},{\"linkId\":\"9d6ffa9f-0110-418c-9ed0-f04910fda2ed\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Recent hospital admission (<3 months)\"},{\"linkId\":\"d2803ff7-25f7-4c7b-ab92-356c49910478\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Major change to regular medication regime\"},{\"linkId\":\"b34af32d-c69d-4d44-889f-5b6d420a7d08\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"Suspected non-adherence to the patient's medication regime \"},{\"linkId\":\"74bad553-c273-41e6-8647-22b860430bc2\",\"answer\":[],\"text\":\"Other\"}]}]}],\"text\":\"The patient has experienced one or more of the following recent significant medical events\"},{\"linkId\":\"ecbf4e5a-d4d1-43eb-9f43-0c0e35fc09c7\",\"answer\":[{\"valueBoolean\":false}],\"text\":\"The Pharmacist has obtained patient consent to take part in the MedsCheck Service or Diabetes MedsCheck Service&nbsp; and share information obtained during the services with other nominated members of the patients healthcare team (such as their GP, diabetes educator) if required\"},{\"linkId\":\"8ef66774-43b0-4190-873f-cfbb6e980aa9\",\"answer\":[],\"text\":\"Question\"}]}}}]}";
FhirValidator val = ourCtxDstu2.newValidator();
val.registerValidatorModule(ourValidator);
ValidationResult result = val.validateWithResult(input);
String encoded = ourCtxDstu2.newJsonParser().setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome());
ourLog.info(encoded);
assertTrue(result.isSuccessful());
}
@Test
public void testParametersHl7OrgDstu2() {
org.hl7.fhir.instance.model.Patient patient = new org.hl7.fhir.instance.model.Patient();

View File

@ -106,18 +106,22 @@
<feature name='hapi-fhir-validation-dstu2' version='${project.version}' start-level='50'>
<feature version='${project.version}'>hapi-fhir-validation</feature>
<feature version='${project.version}'>hapi-fhir-dstu2</feature>
<feature version='${project.version}'>hapi-fhir-r4</feature>
<bundle>mvn:ca.uhn.hapi.fhir/hapi-fhir-validation-resources-dstu2/${project.version}</bundle>
</feature>
<feature name='hapi-fhir-validation-hl7org-dstu2' version='${project.version}' start-level='50'>
<feature version='${project.version}'>hapi-fhir-validation</feature>
<feature version='${project.version}'>hapi-fhir-dstu2</feature>
<feature version='${project.version}'>hapi-fhir-hl7org-dstu2</feature>
<feature version='${project.version}'>hapi-fhir-r4</feature>
<bundle>mvn:ca.uhn.hapi.fhir/hapi-fhir-validation-resources-dstu2/${project.version}</bundle>
</feature>
<feature name='hapi-fhir-validation-dstu2.1' version='${project.version}' start-level='50'>
<feature version='${project.version}'>hapi-fhir-validation</feature>
<feature version='${project.version}'>hapi-fhir-dstu2.1</feature>
<feature version='${project.version}'>hapi-fhir-r4</feature>
<bundle>mvn:ca.uhn.hapi.fhir/hapi-fhir-validation-resources-dstu2.1/${project.version}</bundle>
</feature>

View File

@ -2025,6 +2025,7 @@
<module>hapi-fhir-validation-resources-dstu3</module>
<module>hapi-fhir-structures-r4</module>
<module>hapi-fhir-validation-resources-r4</module>
<module>hapi-fhir-igpacks</module>
<module>hapi-fhir-jaxrsserver-base</module>
<module>hapi-fhir-jaxrsserver-example</module>
<module>hapi-fhir-jpaserver-base</module>
@ -2048,7 +2049,6 @@
<module>example-projects/hapi-fhir-jpaserver-dynamic</module>
<module>tests/hapi-fhir-base-test-mindeps-client</module>
<module>tests/hapi-fhir-base-test-mindeps-server</module>
<module>hapi-fhir-igpacks</module>
<module>hapi-fhir-spring-boot</module>
<!--<module>hapi-fhir-osgi-core</module>-->
</modules>

View File

@ -45,6 +45,13 @@
This work was sponsored by the Regenstrief Institute. Thanks
to Regenstrief for their support!
</action>
<action type="add">
The DSTU2 validator has been refactored to use the same codebase
as the DSTU3/R4 validator (which were harmonized in HAPI FHIR 3.3.0).
This means that we now have a single codebase for all validators, which
improves maintainability and brings a number of improvements
to the accuracy of DSTU2 resource validation.
</action>
<action type="fix">
When encoding a resource that had contained resources with user-supplied
local IDs (e.g. resource.setId("#1")) as well as contained resources
@ -129,6 +136,15 @@
<![CDATA[<code>ConceptMap</code>]]> resource from a CSV; and one for exporting a
<![CDATA[<code>ConceptMap</code>]]> resource to a CSV.
</action>
<action type="add">
Operation methods on a plain server may now use parameters
of type String (i.e. plain Java strings), and any FHIR primitive
datatype will be automatically coerced into a String.
</action>
<action type="add">
The HAPI FHIR CLI now supports importing an IGPack file as an import
to the validation process.
</action>
</release>
<release version="3.3.0" date="2018-03-29">
<action type="add">