Merge validator
This commit is contained in:
parent
a0c40cf98a
commit
adc73e75c9
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,12 +32,20 @@ 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();
|
||||
|
||||
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;
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -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();
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
private 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);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -33,7 +33,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
|
|||
|
||||
@Override
|
||||
public List<StructureDefinition> allStructures() {
|
||||
throw new UnsupportedOperationException();
|
||||
return myValidationSupport.allStructures();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())) {
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue