diff --git a/examples/pom.xml b/examples/pom.xml index 1a0ee2bf0c1..5c9b74052b2 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -35,6 +35,11 @@ hapi-fhir-structures-dstu3 ${project.version} + + ca.uhn.hapi.fhir + hapi-fhir-structures-r4 + ${project.version} + ca.uhn.hapi.fhir hapi-fhir-structures-hl7org-dstu2 diff --git a/examples/src/main/java/example/GenomicsUploader.java b/examples/src/main/java/example/GenomicsUploader.java new file mode 100644 index 00000000000..f7f87823905 --- /dev/null +++ b/examples/src/main/java/example/GenomicsUploader.java @@ -0,0 +1,61 @@ +package example; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.SearchParameter; + +public class GenomicsUploader { + + public static void main(String[] theArgs) { + FhirContext ctx = FhirContext.forR4(); + IGenericClient client = ctx.newRestfulGenericClient("http://fhirtest.uhn.ca/baseR4"); + client.registerInterceptor(new LoggingInterceptor(false)); + + SearchParameter dnaSequenceVariantName = new SearchParameter(); + dnaSequenceVariantName.setId("SearchParameter/dnaSequenceVariantName"); + dnaSequenceVariantName.setStatus(Enumerations.PublicationStatus.ACTIVE); + dnaSequenceVariantName.addBase("Observation"); + dnaSequenceVariantName.setCode("dnaSequenceVariantName"); + dnaSequenceVariantName.setType(Enumerations.SearchParamType.TOKEN); + dnaSequenceVariantName.setTitle("DNASequenceVariantName"); + dnaSequenceVariantName.setExpression("Observation.extension('http://hl7.org/fhir/StructureDefinition/observation-geneticsDNASequenceVariantName')"); + dnaSequenceVariantName.setXpathUsage(SearchParameter.XPathUsageType.NORMAL); + client.update().resource(dnaSequenceVariantName).execute(); + + SearchParameter dNAVariantId = new SearchParameter(); + dNAVariantId.setId("SearchParameter/dNAVariantId"); + dNAVariantId.setStatus(Enumerations.PublicationStatus.ACTIVE); + dNAVariantId.addBase("Observation"); + dNAVariantId.setCode("dnaVariantId"); + dNAVariantId.setType(Enumerations.SearchParamType.TOKEN); + dNAVariantId.setTitle("DNAVariantId"); + dNAVariantId.setExpression("Observation.extension('http://hl7.org/fhir/StructureDefinition/observation-geneticsDNAVariantId')"); + dNAVariantId.setXpathUsage(SearchParameter.XPathUsageType.NORMAL); + client.update().resource(dNAVariantId).execute(); + + SearchParameter gene = new SearchParameter(); + gene.setId("SearchParameter/gene"); + gene.setStatus(Enumerations.PublicationStatus.ACTIVE); + gene.addBase("Observation"); + gene.setCode("gene"); + gene.setType(Enumerations.SearchParamType.TOKEN); + gene.setTitle("Gene"); + gene.setExpression("Observation.extension('http://hl7.org/fhir/StructureDefinition/observation-geneticsGene')"); + gene.setXpathUsage(SearchParameter.XPathUsageType.NORMAL); + client.update().resource(gene).execute(); + + SearchParameter alleleName = new SearchParameter(); + alleleName.setId("SearchParameter/alleleName"); + alleleName.setStatus(Enumerations.PublicationStatus.ACTIVE); + alleleName.addBase("Observation"); + alleleName.setCode("alleleName"); + alleleName.setType(Enumerations.SearchParamType.TOKEN); + alleleName.setTitle("AlleleName"); + alleleName.setExpression("Observation.extension('http://hl7.org/fhir/StructureDefinition/observation-geneticsAlleleName')"); + alleleName.setXpathUsage(SearchParameter.XPathUsageType.NORMAL); + client.update().resource(alleleName).execute(); + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IContextValidationSupport.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IContextValidationSupport.java index 8a068739346..126efe344d8 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IContextValidationSupport.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IContextValidationSupport.java @@ -20,117 +20,113 @@ package ca.uhn.fhir.context.support; * #L% */ -import java.util.List; - +import ca.uhn.fhir.context.FhirContext; import org.hl7.fhir.instance.model.api.IBaseResource; -import ca.uhn.fhir.context.FhirContext; +import java.util.List; public interface IContextValidationSupport { - /** - * Expands the given portion of a ValueSet - * - * @param theInclude - * The portion to include - * @return The expansion - */ - EVS_OUT expandValueSet(FhirContext theContext, EVS_IN theInclude); + /** + * Expands the given portion of a ValueSet + * + * @param theInclude The portion to include + * @return The expansion + */ + EVS_OUT expandValueSet(FhirContext theContext, EVS_IN theInclude); - /** - * Load and return all possible structure definitions - */ - List fetchAllStructureDefinitions(FhirContext theContext); + /** + * Load and return all conformance resources associated with this + * validation support module. This method may return null if it doesn't + * make sense for a given module. + */ + List fetchAllConformanceResources(FhirContext theContext); - - /** - * Fetch a code system by ID - * - * @param theSystem - * The code system - * @return The valueset (must not be null, but can be an empty ValueSet) - */ - CST fetchCodeSystem(FhirContext theContext, String theSystem); + /** + * Load and return all possible structure definitions + */ + List fetchAllStructureDefinitions(FhirContext theContext); - /** - * Loads a resource needed by the validation (a StructureDefinition, or a - * ValueSet) - * - * @param theContext - * The HAPI FHIR Context object current in use by the validator - * @param theClass - * The type of the resource to load - * @param theUri - * The resource URI - * @return Returns the resource, or null if no resource with the - * given URI can be found - */ - T fetchResource(FhirContext theContext, Class theClass, String theUri); + /** + * Fetch a code system by ID + * + * @param theSystem The code system + * @return The valueset (must not be null, but can be an empty ValueSet) + */ + CST fetchCodeSystem(FhirContext theContext, String theSystem); - SDT fetchStructureDefinition(FhirContext theCtx, String theUrl); + /** + * Loads a resource needed by the validation (a StructureDefinition, or a + * ValueSet) + * + * @param theContext The HAPI FHIR Context object current in use by the validator + * @param theClass The type of the resource to load + * @param theUri The resource URI + * @return Returns the resource, or null if no resource with the + * given URI can be found + */ + T fetchResource(FhirContext theContext, Class theClass, String theUri); - /** - * Returns true if codes in the given code system can be expanded - * or validated - * - * @param theSystem - * The URI for the code system, e.g. "http://loinc.org" - * @return Returns true if codes in the given code system can be - * validated - */ - boolean isCodeSystemSupported(FhirContext theContext, String theSystem); + SDT fetchStructureDefinition(FhirContext theCtx, String theUrl); -/** - * Validates that the given code exists and if possible returns a display - * name. This method is called to check codes which are found in "example" - * binding fields (e.g. Observation.code in the default profile. - * - * @param theCodeSystem - * The code system, e.g. "http://loinc.org" - * @param theCode - * The code, e.g. "1234-5" - * @param theDisplay - * The display name, if it should also be validated - * @return Returns a validation result object - */ - CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay); + /** + * Returns true if codes in the given code system can be expanded + * or validated + * + * @param theSystem The URI for the code system, e.g. "http://loinc.org" + * @return Returns true if codes in the given code system can be + * validated + */ + boolean isCodeSystemSupported(FhirContext theContext, String theSystem); + + /** + * Validates that the given code exists and if possible returns a display + * name. This method is called to check codes which are found in "example" + * binding fields (e.g. Observation.code in the default profile. + * + * @param theCodeSystem The code system, e.g. "http://loinc.org" + * @param theCode The code, e.g. "1234-5" + * @param theDisplay The display name, if it should also be validated + * @return Returns a validation result object + */ + CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay); public class CodeValidationResult { - private CDCT definition; - private String message; - private IST severity; - - public CodeValidationResult(CDCT theNext) { - this.definition = theNext; - } - - public CodeValidationResult(IST severity, String message) { - this.severity = severity; - this.message = message; - } - - public CodeValidationResult(IST severity, String message, CDCT definition) { - this.severity = severity; - this.message = message; - this.definition = definition; - } - - public CDCT asConceptDefinition() { - return definition; - } - - public String getMessage() { - return message; - } - - public IST getSeverity() { - return severity; - } - - public boolean isOk() { - return definition != null; - } - - } + private CDCT definition; + private String message; + private IST severity; + + public CodeValidationResult(CDCT theNext) { + this.definition = theNext; + } + + public CodeValidationResult(IST severity, String message) { + this.severity = severity; + this.message = message; + } + + public CodeValidationResult(IST severity, String message, CDCT definition) { + this.severity = severity; + this.message = message; + this.definition = definition; + } + + public CDCT asConceptDefinition() { + return definition; + } + + public String getMessage() { + return message; + } + + public IST getSeverity() { + return severity; + } + + public boolean isOk() { + return definition != null; + } + + } } diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml index 320090b2643..3d68cdcca53 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml @@ -73,6 +73,11 @@ hapi-fhir-validation-resources-dstu3 ${project.version} + + ca.uhn.hapi.fhir + hapi-fhir-igpacks + ${project.version} + ch.qos.logback diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/App.java b/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/App.java index 4f882dfd986..5d34fbd0706 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/App.java +++ b/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/App.java @@ -41,6 +41,7 @@ public class App { ourCommands.add(new ValidationDataUploader()); ourCommands.add(new WebsocketSubscribeCommand()); ourCommands.add(new UploadTerminologyCommand()); + ourCommands.add(new IgPackUploader()); Collections.sort(ourCommands); } diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/BaseCommand.java b/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/BaseCommand.java index d854871a5e2..62b8e99027d 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/BaseCommand.java +++ b/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/BaseCommand.java @@ -1,53 +1,99 @@ package ca.uhn.fhir.cli; -import org.apache.commons.cli.*; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.rest.client.api.IGenericClient; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.time.DateUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.fusesource.jansi.Ansi; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; public abstract class BaseCommand implements Comparable { private static final String SPEC_DEFAULT_VERSION = "dstu3"; - + private static final Logger ourLog = LoggerFactory.getLogger(BaseCommand.class); private FhirContext myFhirCtx; public BaseCommand() { super(); } + protected void addFhirVersionOption(Options theOptions) { + Option opt = new Option("f", "fhirversion", true, "Spec version to upload (default is '" + SPEC_DEFAULT_VERSION + "')"); + opt.setRequired(false); + theOptions.addOption(opt); + } + @Override public int compareTo(BaseCommand theO) { return getCommandName().compareTo(theO.getCommandName()); } + private void downloadFileFromInternet(CloseableHttpResponse result, File localFile) throws IOException { + FileOutputStream buffer = FileUtils.openOutputStream(localFile); + try { + + long maxLength = result.getEntity().getContentLength(); + long nextLog = -1; + // ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int nRead; + byte[] data = new byte[16384]; + while ((nRead = result.getEntity().getContent().read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + long fileSize = FileUtils.sizeOf(localFile); + if (fileSize > nextLog) { + System.err.print("\r" + Ansi.ansi().eraseLine()); + System.err.print(FileUtils.byteCountToDisplaySize(fileSize)); + if (maxLength > 0) { + System.err.print(" ["); + int stars = (int) (50.0f * ((float) fileSize / (float) maxLength)); + for (int i = 0; i < stars; i++) { + System.err.print("*"); + } + for (int i = stars; i < 50; i++) { + System.err.print(" "); + } + System.err.print("]"); + } + System.err.flush(); + nextLog += 100000; + } + } + buffer.flush(); + + System.err.println(); + System.err.flush(); + } finally { + IOUtils.closeQuietly(buffer); + } + } + public abstract String getCommandDescription(); public abstract String getCommandName(); public abstract Options getOptions(); - protected IGenericClient newClient(FhirContext ctx, String theBaseUrl) { - ctx.getRestfulClientFactory().setSocketTimeout(10 * 60 * 1000); - IGenericClient fhirClient = ctx.newRestfulGenericClient(theBaseUrl); - return fhirClient; - } - - public abstract void run(CommandLine theCommandLine) throws ParseException, Exception; - -// public FhirContext getFhirCtx() { -// if (myFhirCtx == null) { -// myFhirCtx = FhirContext.forDstu2(); -// } -// return myFhirCtx; -// } - - protected void addFhirVersionOption(Options theOptions) { - Option opt = new Option("f", "fhirversion", true, "Spec version to upload (default is '" + SPEC_DEFAULT_VERSION + "')"); - opt.setRequired(false); - theOptions.addOption(opt); - } - protected FhirContext getSpecVersionContext(CommandLine theCommandLine) throws ParseException { if (myFhirCtx == null) { String specVersion = theCommandLine.getOptionValue("f", SPEC_DEFAULT_VERSION); @@ -68,4 +114,82 @@ public abstract class BaseCommand implements Comparable { return myFhirCtx; } +// public FhirContext getFhirCtx() { +// if (myFhirCtx == null) { +// myFhirCtx = FhirContext.forDstu2(); +// } +// return myFhirCtx; +// } + + protected Collection loadFile(FhirContext theCtx, String theSpecUrl, String theFilepath, boolean theCacheFile) throws IOException { + String userHomeDir = System.getProperty("user.home"); + + File applicationDir = new File(userHomeDir + File.separator + "." + "hapi-fhir-cli"); + FileUtils.forceMkdir(applicationDir); + + Collection inputFiles; + if (isNotBlank(theFilepath)) { + ourLog.info("Loading from local path: {}", theFilepath); + + if (theFilepath.startsWith("~" + File.separator)) { + theFilepath = userHomeDir + theFilepath.substring(1); + } + + File suppliedFile = new File(FilenameUtils.normalize(theFilepath)); + + if (suppliedFile.isDirectory()) { + inputFiles = FileUtils.listFiles(suppliedFile, new String[]{"zip"}, false); + } else { + inputFiles = Collections.singletonList(suppliedFile); + } + + } else { + + File cacheDir = new File(applicationDir, "cache"); + FileUtils.forceMkdir(cacheDir); + + File inputFile = new File(cacheDir, "examples-json-" + theCtx.getVersion().getVersion() + ".zip"); + + Date cacheExpiryDate = DateUtils.addHours(new Date(), -12); + + if (!inputFile.exists() | (theCacheFile && FileUtils.isFileOlder(inputFile, cacheExpiryDate))) { + + File exampleFileDownloading = new File(cacheDir, "examples-json-" + theCtx.getVersion().getVersion() + ".zip.partial"); + + HttpGet get = new HttpGet(theSpecUrl); + CloseableHttpClient client = HttpClientBuilder.create().build(); + CloseableHttpResponse result = client.execute(get); + + if (result.getStatusLine().getStatusCode() != 200) { + throw new CommandFailureException("Got HTTP " + result.getStatusLine().getStatusCode() + " response code loading " + theSpecUrl); + } + + ourLog.info("Downloading from remote url: {}", theSpecUrl); + downloadFileFromInternet(result, exampleFileDownloading); + + FileUtils.deleteQuietly(inputFile); + FileUtils.moveFile(exampleFileDownloading, inputFile); + + if (!theCacheFile) { + inputFile.deleteOnExit(); + } + + ourLog.info("Successfully Loaded example pack ({})", FileUtils.byteCountToDisplaySize(FileUtils.sizeOf(inputFile))); + IOUtils.closeQuietly(result.getEntity().getContent()); + } + + inputFiles = Collections.singletonList(inputFile); + + } + return inputFiles; + } + + protected IGenericClient newClient(FhirContext ctx, String theBaseUrl) { + ctx.getRestfulClientFactory().setSocketTimeout(10 * 60 * 1000); + IGenericClient fhirClient = ctx.newRestfulGenericClient(theBaseUrl); + return fhirClient; + } + + public abstract void run(CommandLine theCommandLine) throws ParseException, Exception; + } diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java b/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java index c88aca5aba2..f36ee10cabb 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java +++ b/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java @@ -127,79 +127,17 @@ public class ExampleDataUploader extends BaseCommand { boolean cacheFile = theCommandLine.hasOption('c'); - String userHomeDir = System.getProperty("user.home"); - - File applicationDir = new File(userHomeDir + File.separator + "." + "hapi-fhir-cli"); - FileUtils.forceMkdir(applicationDir); - - if (isNotBlank(filepath)) { - ourLog.info("Loading from local path: {}", filepath); - - if (filepath.startsWith("~" + File.separator)) { - filepath = userHomeDir + filepath.substring(1); - } - - File suppliedFile = new File(FilenameUtils.normalize(filepath)); - - if (suppliedFile.isDirectory()) { - Collection inputFiles; - inputFiles = FileUtils.listFiles(suppliedFile, new String[]{"zip"}, false); - - for (File inputFile : inputFiles) { - IBaseBundle bundle = getBundleFromFile(limit, inputFile, ctx); - processBundle(ctx, bundle); - sendBundleToTarget(targetServer, ctx, bundle); - } - } else { - IBaseBundle bundle = getBundleFromFile(limit, suppliedFile, ctx); - processBundle(ctx, bundle); - sendBundleToTarget(targetServer, ctx, bundle); - } - - } else { - - File cacheDir = new File(applicationDir, "cache"); - FileUtils.forceMkdir(cacheDir); - - File inputFile = new File(cacheDir, "examples-json-" + ctx.getVersion().getVersion() + ".zip"); - - Date cacheExpiryDate = DateUtils.addHours(new Date(), -12); - - if (!inputFile.exists() | (cacheFile && FileUtils.isFileOlder(inputFile, cacheExpiryDate))) { - - File exampleFileDownloading = new File(cacheDir, "examples-json-" + ctx.getVersion().getVersion() + ".zip.partial"); - - HttpGet get = new HttpGet(specUrl); - CloseableHttpClient client = HttpClientBuilder.create().build(); - CloseableHttpResponse result = client.execute(get); - - if (result.getStatusLine().getStatusCode() != 200) { - throw new CommandFailureException("Got HTTP " + result.getStatusLine().getStatusCode() + " response code loading " + specUrl); - } - - ourLog.info("Downloading from remote url: {}", specUrl); - downloadFileFromInternet(result, exampleFileDownloading); - - FileUtils.deleteQuietly(inputFile); - FileUtils.moveFile(exampleFileDownloading, inputFile); - - if (!cacheFile) { - inputFile.deleteOnExit(); - } - - ourLog.info("Successfully Loaded example pack ({})", FileUtils.byteCountToDisplaySize(FileUtils.sizeOf(inputFile))); - IOUtils.closeQuietly(result.getEntity().getContent()); - } + Collection inputFiles = loadFile(ctx, specUrl, filepath, cacheFile); + for (File inputFile : inputFiles) { IBaseBundle bundle = getBundleFromFile(limit, inputFile, ctx); processBundle(ctx, bundle); - sendBundleToTarget(targetServer, ctx, bundle); - } } + private IBaseBundle getBundleFromFile(Integer theLimit, File theSuppliedFile, FhirContext theCtx) throws ParseException, IOException { switch (theCtx.getVersion().getVersion()) { case DSTU2: @@ -791,43 +729,5 @@ public class ExampleDataUploader extends BaseCommand { return bundle; } - private void downloadFileFromInternet(CloseableHttpResponse result, File localFile) throws IOException { - FileOutputStream buffer = FileUtils.openOutputStream(localFile); - try { - - long maxLength = result.getEntity().getContentLength(); - long nextLog = -1; - // ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - int nRead; - byte[] data = new byte[16384]; - while ((nRead = result.getEntity().getContent().read(data, 0, data.length)) != -1) { - buffer.write(data, 0, nRead); - long fileSize = FileUtils.sizeOf(localFile); - if (fileSize > nextLog) { - System.err.print("\r" + Ansi.ansi().eraseLine()); - System.err.print(FileUtils.byteCountToDisplaySize(fileSize)); - if (maxLength > 0) { - System.err.print(" ["); - int stars = (int) (50.0f * ((float) fileSize / (float) maxLength)); - for (int i = 0; i < stars; i++) { - System.err.print("*"); - } - for (int i = stars; i < 50; i++) { - System.err.print(" "); - } - System.err.print("]"); - } - System.err.flush(); - nextLog += 100000; - } - } - buffer.flush(); - - System.err.println(); - System.err.flush(); - } finally { - IOUtils.closeQuietly(buffer); - } - } } diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/IgPackUploader.java b/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/IgPackUploader.java new file mode 100644 index 00000000000..bb3440f7e1f --- /dev/null +++ b/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/IgPackUploader.java @@ -0,0 +1,82 @@ +package ca.uhn.fhir.cli; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport; +import org.hl7.fhir.dstu3.model.StructureDefinition; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import parser.IgPackParserDstu3; +import parser.IgPackValidationSupportDstu3; + +import java.io.File; +import java.io.FileInputStream; +import java.util.Collection; + +public class IgPackUploader extends BaseCommand { + private static final Logger ourLog = LoggerFactory.getLogger(IgPackUploader.class); + + @Override + public String getCommandDescription() { + return "Uploads an Implementation Guide Validation Pack"; + } + + @Override + public String getCommandName() { + return "upload-igpack"; + } + + @Override + public Options getOptions() { + Options options = new Options(); + addFhirVersionOption(options); + + Option opt = new Option("t", "target", true, "Base URL for the target server (e.g. \"http://example.com/fhir\")"); + opt.setRequired(false); + options.addOption(opt); + + opt = new Option("u", "url", true, "The URL to the validation.pack file, e.g. http://hl7.org/fhir/us/core/validator.pack"); + opt.setRequired(true); + options.addOption(opt); + + return options; + } + + @Override + public void run(CommandLine theCommandLine) throws ParseException, Exception { + FhirContext ctx = getSpecVersionContext(theCommandLine); + + String targetServer = theCommandLine.getOptionValue("t"); + IGenericClient client = ctx.newRestfulGenericClient(targetServer); + + String url = theCommandLine.getOptionValue("u"); + + Collection files = loadFile(ctx, url, null, false); + for (File nextFile : files) { + switch (ctx.getVersion().getVersion()) { + case DSTU3: + IgPackParserDstu3 packParser = new IgPackParserDstu3(ctx); + IValidationSupport ig = packParser.parseIg(new FileInputStream(nextFile), nextFile.getName()); + Iterable conformanceResources = ig.fetchAllConformanceResources(ctx); + for (IBaseResource nextResource : conformanceResources) { + String nextResourceUrl = ((IPrimitiveType)ctx.newTerser().getSingleValueOrNull(nextResource, "url")).getValueAsString(); + ourLog.info("Uploading resource: {}", nextResourceUrl); + client + .update() + .resource(nextResource) + .conditional() + .and(StructureDefinition.URL.matches().value(nextResourceUrl)) + .execute(); + } + default: + throw new ParseException("This command does not support FHIR version " + ctx.getVersion().getVersion()); + } + } + } +} diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu3.java b/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu3.java index ff9eca35cd8..6331acef87b 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu3.java +++ b/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu3.java @@ -1,8 +1,9 @@ package ca.uhn.fhir.cli; -import java.util.Collections; -import java.util.List; - +import ca.uhn.fhir.context.FhirContext; +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 org.hl7.fhir.dstu3.hapi.validation.IValidationSupport; import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.StructureDefinition; @@ -10,10 +11,8 @@ import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.instance.model.api.IBaseResource; -import ca.uhn.fhir.context.FhirContext; -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.Collections; +import java.util.List; public class LoadingValidationSupportDstu3 implements IValidationSupport { @@ -26,6 +25,11 @@ public class LoadingValidationSupportDstu3 implements IValidationSupport { return null; } + @Override + public List fetchAllConformanceResources(FhirContext theContext) { + return null; + } + @Override public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { return null; diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportR4.java b/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportR4.java index f04edfa13ea..a065ef2386a 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportR4.java +++ b/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportR4.java @@ -1,30 +1,38 @@ package ca.uhn.fhir.cli; -import java.util.Collections; -import java.util.List; - -import org.hl7.fhir.r4.model.CodeSystem; -import org.hl7.fhir.r4.model.StructureDefinition; -import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent; -import org.hl7.fhir.instance.model.api.IBaseResource; - import ca.uhn.fhir.context.FhirContext; 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 org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent; + +import java.util.Collections; +import java.util.List; public class LoadingValidationSupportR4 implements org.hl7.fhir.r4.hapi.ctx.IValidationSupport { - private FhirContext myCtx = FhirContext.forR4(); - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoadingValidationSupportR4.class); + private FhirContext myCtx = FhirContext.forR4(); @Override public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { return null; } + @Override + public List fetchAllConformanceResources(FhirContext theContext) { + return null; + } + + @Override + public List fetchAllStructureDefinitions(FhirContext theContext) { + return Collections.emptyList(); + } + @Override public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { return null; @@ -34,10 +42,10 @@ public class LoadingValidationSupportR4 implements org.hl7.fhir.r4.hapi.ctx.IVal public T fetchResource(FhirContext theContext, Class theClass, String theUri) { String resName = myCtx.getResourceDefinition(theClass).getName(); ourLog.info("Attempting to fetch {} at URL: {}", resName, theUri); - + myCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); IGenericClient client = myCtx.newRestfulGenericClient("http://example.com"); - + T result; try { result = client.read(theClass, theUri); @@ -63,9 +71,4 @@ public class LoadingValidationSupportR4 implements org.hl7.fhir.r4.hapi.ctx.IVal return null; } - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - return Collections.emptyList(); - } - } diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/src/test/java/InstallIgPackTest.java b/hapi-fhir-cli/hapi-fhir-cli-app/src/test/java/InstallIgPackTest.java new file mode 100644 index 00000000000..70d05ee5f67 --- /dev/null +++ b/hapi-fhir-cli/hapi-fhir-cli-app/src/test/java/InstallIgPackTest.java @@ -0,0 +1,11 @@ +import org.junit.Test; + +public class InstallIgPackTest { + + @Test + public void testInstallIgPack() { + + } + + +} diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/src/test/java/ValidateTest.java b/hapi-fhir-cli/hapi-fhir-cli-app/src/test/java/ValidateTest.java index 0025053d860..d15f687c70e 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-app/src/test/java/ValidateTest.java +++ b/hapi-fhir-cli/hapi-fhir-cli-app/src/test/java/ValidateTest.java @@ -13,12 +13,9 @@ public class ValidateTest { @Test public void testValidateLocalProfile() { -// String profilePath = ValidateTest.class.getResource("/uslab-patient.profile.xml").getFile(); String resourcePath = ValidateTest.class.getResource("/patient-uslab-example1.xml").getFile(); -// ourLog.info(profilePath); ourLog.info(resourcePath); -// App.main(new String[] {"validate", "-p", "-n", resourcePath, "-l", profilePath}); App.main(new String[] {"validate", "-p", "-n", resourcePath}); } diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/LoggingInterceptor.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/LoggingInterceptor.java index 4d7ecc760ea..d25efc8871f 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/LoggingInterceptor.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/LoggingInterceptor.java @@ -26,6 +26,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.Constants; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.Validate; @@ -107,7 +108,12 @@ public class LoggingInterceptor implements IClientInterceptor { */ List locationHeaders = theResponse.getHeaders(Constants.HEADER_LOCATION); if (locationHeaders != null && locationHeaders.size() > 0) { - respLocation = " (Location: " + locationHeaders.get(0) + ")"; + String locationValue = locationHeaders.get(0); + IdDt locationValueId = new IdDt(locationValue); + if (locationValueId.hasBaseUrl() && locationValueId.hasIdPart()) { + locationValue = locationValueId.toUnqualified().getValue(); + } + respLocation = " (" + locationValue + ")"; } myLog.info("Client response: {}{}", message, respLocation); } diff --git a/hapi-fhir-igpacks/pom.xml b/hapi-fhir-igpacks/pom.xml new file mode 100644 index 00000000000..02a758e9903 --- /dev/null +++ b/hapi-fhir-igpacks/pom.xml @@ -0,0 +1,46 @@ + + + + hapi-deployable-pom + ca.uhn.hapi.fhir + 3.0.0-SNAPSHOT + ../hapi-deployable-pom/pom.xml + + 4.0.0 + + hapi-fhir-igpacks + + + + ca.uhn.hapi.fhir + hapi-fhir-base + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-jpaserver-base + ${project.version} + + + + ca.uhn.hapi.fhir + hapi-fhir-structures-dstu3 + ${project.version} + true + + + + ch.qos.logback + logback-classic + + + + + + + + + + diff --git a/hapi-fhir-igpacks/src/main/java/parser/IgPackParserDstu3.java b/hapi-fhir-igpacks/src/main/java/parser/IgPackParserDstu3.java new file mode 100644 index 00000000000..dca92fa0a25 --- /dev/null +++ b/hapi-fhir-igpacks/src/main/java/parser/IgPackParserDstu3.java @@ -0,0 +1,121 @@ +package parser; + + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.jpa.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.validation.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 IgPackParserDstu3(FhirContext theCtx) { + FhirVersionEnum expectedVersion = FhirVersionEnum.DSTU3; + Validate.isTrue(theCtx.getVersion().getVersion() == expectedVersion, "theCtx is not for the correct version, expecting " + expectedVersion); + + myCtx = theCtx; + } + + private IBaseResource findResource(Map 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 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 candidateResources = new HashMap<>(); + Map 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 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); + } + + + } + +} diff --git a/hapi-fhir-igpacks/src/main/java/parser/IgPackValidationSupportDstu3.java b/hapi-fhir-igpacks/src/main/java/parser/IgPackValidationSupportDstu3.java new file mode 100644 index 00000000000..c0cd82d0312 --- /dev/null +++ b/hapi-fhir-igpacks/src/main/java/parser/IgPackValidationSupportDstu3.java @@ -0,0 +1,104 @@ +package parser; + +import ca.uhn.fhir.context.FhirContext; +import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport; +import org.hl7.fhir.dstu3.model.CodeSystem; +import org.hl7.fhir.dstu3.model.ConceptMap; +import org.hl7.fhir.dstu3.model.StructureDefinition; +import org.hl7.fhir.dstu3.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 IgPackValidationSupportDstu3 implements IValidationSupport { + private final Map myIgResources; + + public IgPackValidationSupportDstu3(Map theIgResources) { + myIgResources = theIgResources; + } + + @Override + public ValueSet.ValueSetExpansionComponent expandValueSet(FhirContext theContext, ValueSet.ConceptSetComponent theInclude) { + return null; + } + + @Override + public List fetchAllConformanceResources(FhirContext theContext) { + return new ArrayList<>(myIgResources.values()); + } + + + @Override + public List fetchAllStructureDefinitions(FhirContext theContext) { + ArrayList retVal = new ArrayList<>(); + for (Map.Entry next : myIgResources.entrySet()) { + if (next.getKey().getResourceType().equals("StructureDefinition")) { + retVal.add((StructureDefinition) next.getValue()); + } + } + return retVal; + } + + @Override + public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { + return fetchResource(theContext, CodeSystem.class, theSystem); + } + + @Override + public T fetchResource(FhirContext theContext, Class theClass, String theUri) { + for (Map.Entry next : myIgResources.entrySet()) { + if (theClass.equals(CodeSystem.class)) { + if (theClass.isAssignableFrom(next.getValue().getClass())) { + CodeSystem sd = ((CodeSystem) next.getValue()); + if (sd.getUrl().equals(theUri)) { + return (T) sd; + } + } + } + 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 StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { + return fetchResource(theCtx, StructureDefinition.class, theUrl); + } + + @Override + public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { + return false; + } + + @Override + public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { + return null; + } +} diff --git a/hapi-fhir-igpacks/src/test/java/ca/uhn/fhir/igpack/parser/IgPackParserDstu3Test.java b/hapi-fhir-igpacks/src/test/java/ca/uhn/fhir/igpack/parser/IgPackParserDstu3Test.java new file mode 100644 index 00000000000..4dc8244f659 --- /dev/null +++ b/hapi-fhir-igpacks/src/test/java/ca/uhn/fhir/igpack/parser/IgPackParserDstu3Test.java @@ -0,0 +1,30 @@ +package ca.uhn.fhir.igpack.parser; + + +import ca.uhn.fhir.context.FhirContext; +import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport; +import org.hl7.fhir.dstu3.model.ValueSet; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import parser.IgPackParserDstu3; + +import static org.junit.Assert.*; + +public class IgPackParserDstu3Test { + private static final Logger ourLog = LoggerFactory.getLogger(IgPackParserDstu3Test.class); + + @Test + public void testParseIg() { + + FhirContext ctx = FhirContext.forDstu3(); + IgPackParserDstu3 igParser = new IgPackParserDstu3(ctx); + + IValidationSupport result = igParser.parseIg(IgPackParserDstu3Test.class.getResourceAsStream("/us-core-stu3-validator.pack"), "US-Core STU3"); + + assertNotNull(result.fetchResource(ctx, ValueSet.class, "http://hl7.org/fhir/us/core/ValueSet/simple-language")); + assertEquals(50, result.fetchAllConformanceResources(ctx).size()); + } + +} diff --git a/hapi-fhir-igpacks/src/test/resources/logback-test.xml b/hapi-fhir-igpacks/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..e5cbbb9c22e --- /dev/null +++ b/hapi-fhir-igpacks/src/test/resources/logback-test.xml @@ -0,0 +1,30 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] - %msg%n + + + + + + + + + + + + + + + + + + + + + diff --git a/hapi-fhir-igpacks/src/test/resources/us-core-stu3-validator.pack b/hapi-fhir-igpacks/src/test/resources/us-core-stu3-validator.pack new file mode 100644 index 00000000000..3c027fe61c6 Binary files /dev/null and b/hapi-fhir-igpacks/src/test/resources/us-core-stu3-validator.pack differ diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index e750cbb110b..a31753e9529 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -243,16 +243,20 @@ public abstract class BaseHapiFhirDao implements IDao { if (nextParam.getParamName().equals(nextCompositeOf.getName())) { IQueryParameterType nextParamAsClientParam = nextParam.toQueryParameterType(); String value = nextParamAsClientParam.getValueAsQueryToken(getContext()); - value = UrlUtil.escape(value); - nextChoicesList.add(key + "=" + value); + if (isNotBlank(value)) { + value = UrlUtil.escape(value); + nextChoicesList.add(key + "=" + value); + } } } } if (linksForCompositePart != null) { for (ResourceLink nextLink : linksForCompositePart) { String value = nextLink.getTargetResource().getIdDt().toUnqualifiedVersionless().getValue(); - value = UrlUtil.escape(value); - nextChoicesList.add(key + "=" + value); + if (isNotBlank(value)) { + value = UrlUtil.escape(value); + nextChoicesList.add(key + "=" + value); + } } } } @@ -260,7 +264,9 @@ public abstract class BaseHapiFhirDao implements IDao { Set queryStringsToPopulate = extractCompositeStringUniquesValueChains(theEntity.getResourceType(), partsChoices); for (String nextQueryString : queryStringsToPopulate) { - compositeStringUniques.add(new ResourceIndexedCompositeStringUnique(theEntity, nextQueryString)); + if (isNotBlank(nextQueryString)) { + compositeStringUniques.add(new ResourceIndexedCompositeStringUnique(theEntity, nextQueryString)); + } } } @@ -1562,7 +1568,7 @@ public abstract class BaseHapiFhirDao implements IDao { theEntity.setParamsUriPopulated(uriParams.isEmpty() == false); theEntity.setParamsCoords(coordsParams); theEntity.setParamsCoordsPopulated(coordsParams.isEmpty() == false); - theEntity.setParamsCompositeStringUnique(compositeStringUniques); +// theEntity.setParamsCompositeStringUnique(compositeStringUniques); theEntity.setParamsCompositeStringUniquePresent(compositeStringUniques.isEmpty() == false); theEntity.setResourceLinks(links); theEntity.setHasLinks(links.isEmpty() == false); @@ -1715,11 +1721,24 @@ public abstract class BaseHapiFhirDao implements IDao { theEntity.setResourceLinks(links); // Store composite string uniques - for (ResourceIndexedCompositeStringUnique next : existingCompositeStringUniques) { - myEntityManager.remove(next); - } - for (ResourceIndexedCompositeStringUnique next : compositeStringUniques) { - myEntityManager.persist(next); + if (getConfig().isUniqueIndexesEnabled()) { + for (ResourceIndexedCompositeStringUnique next : existingCompositeStringUniques) { + if (!compositeStringUniques.contains(next)) { + myEntityManager.remove(next); + } + } + for (ResourceIndexedCompositeStringUnique next : compositeStringUniques) { + if (!existingCompositeStringUniques.contains(next)) { + if (myConfig.isUniqueIndexesCheckedBeforeSave()) { + ResourceIndexedCompositeStringUnique existing = myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString()); + if (existing != null) { + throw new PreconditionFailedException("Can not create resource of type " + theEntity.getResourceType() + " as it would create a duplicate index matching query: " + next.getIndexString() + " (existing index belongs to " + existing.getResource().getIdDt().toUnqualifiedVersionless().getValue() + ")"); + } + } + ourLog.debug("Persisting unique index: {}", next); + myEntityManager.persist(next); + } + } } theEntity.toString(); @@ -1897,6 +1916,21 @@ public abstract class BaseHapiFhirDao implements IDao { */ public static Set extractCompositeStringUniquesValueChains(String theResourceType, List> thePartsChoices) { + for (List next : thePartsChoices) { + for (Iterator iter = next.iterator(); iter.hasNext(); ) { + if (isBlank(iter.next())) { + iter.remove(); + } + } + if (next.isEmpty()) { + return Collections.emptySet(); + } + } + + if (thePartsChoices.isEmpty()) { + return Collections.emptySet(); + } + Collections.sort(thePartsChoices, new Comparator>() { @Override public int compare(List o1, List o2) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java index 734a7f92c8e..be2fc9c3aac 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java @@ -75,10 +75,64 @@ public class DaoConfig { private boolean myDeleteStaleSearches = true; private boolean myEnforceReferentialIntegrityOnDelete = true; + private boolean myUniqueIndexesEnabled = true; + /** + * If set to true (default is true), indexes will be + * created for search parameters marked as {@link ca.uhn.fhir.jpa.util.JpaConstants#EXT_SP_UNIQUE}. + * This is a HAPI FHIR specific extension which can be used to specify that no more than one + * resource can exist which matches a given criteria, using a database constraint to + * enforce this. + */ + public boolean isUniqueIndexesEnabled() { + return myUniqueIndexesEnabled; + } + + /** + * If set to true (default is true), indexes will be + * created for search parameters marked as {@link ca.uhn.fhir.jpa.util.JpaConstants#EXT_SP_UNIQUE}. + * This is a HAPI FHIR specific extension which can be used to specify that no more than one + * resource can exist which matches a given criteria, using a database constraint to + * enforce this. + */ + public void setUniqueIndexesEnabled(boolean theUniqueIndexesEnabled) { + myUniqueIndexesEnabled = theUniqueIndexesEnabled; + } + + /** + * When using {@link #setUniqueIndexesEnabled(boolean) unique indexes}, if this + * setting is set to true (default is true) the system + * will test for the existence of a particular unique index value prior to saving + * a new one. + *

+ * This causes friendlier error messages to be generated, but adds an + * extra round-trip to the database for eavh save so it can cause + * a small performance hit. + *

+ */ + public boolean isUniqueIndexesCheckedBeforeSave() { + return myUniqueIndexesCheckedBeforeSave; + } + + /** + * When using {@link #setUniqueIndexesEnabled(boolean) unique indexes}, if this + * setting is set to true (default is true) the system + * will test for the existence of a particular unique index value prior to saving + * a new one. + *

+ * This causes friendlier error messages to be generated, but adds an + * extra round-trip to the database for eavh save so it can cause + * a small performance hit. + *

+ */ + public void setUniqueIndexesCheckedBeforeSave(boolean theUniqueIndexesCheckedBeforeSave) { + myUniqueIndexesCheckedBeforeSave = theUniqueIndexesCheckedBeforeSave; + } + + private boolean myUniqueIndexesCheckedBeforeSave = true; private boolean myEnforceReferentialIntegrityOnWrite = true; - private int myEverythingIncludesFetchPageSize = 50; + /** * update setter javadoc if default changes */ diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java index eace417ea9b..d9d36c3f608 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java @@ -1258,33 +1258,35 @@ public class SearchBuilder implements ISearchBuilder { * Check if there is a unique key associated with the set * of parameters passed in */ - if (myParams.getIncludes().isEmpty()) { - if (myParams.getRevIncludes().isEmpty()) { - if (myParams.getEverythingMode() == null) { - if (myParams.isAllParametersHaveNoModifier()) { - Set paramNames = theParams.keySet(); - if (paramNames.isEmpty() == false) { - List searchParams = mySearchParamRegistry.getActiveUniqueSearchParams(myResourceName, paramNames); - if (searchParams.size() > 0) { - List> params = new ArrayList<>(); - for (Entry>> nextParamNameToValues : theParams.entrySet()) { - String nextParamName = nextParamNameToValues.getKey(); - nextParamName = UrlUtil.escape(nextParamName); - for (List nextAnd : nextParamNameToValues.getValue()) { - ArrayList nextValueList = new ArrayList<>(); - params.add(nextValueList); - for (IQueryParameterType nextOr : nextAnd) { - String nextOrValue = nextOr.getValueAsQueryToken(myContext); - nextOrValue = UrlUtil.escape(nextOrValue); - nextValueList.add(nextParamName + "=" + nextOrValue); + if (myCallingDao.getConfig().isUniqueIndexesEnabled()) { + if (myParams.getIncludes().isEmpty()) { + if (myParams.getRevIncludes().isEmpty()) { + if (myParams.getEverythingMode() == null) { + if (myParams.isAllParametersHaveNoModifier()) { + Set paramNames = theParams.keySet(); + if (paramNames.isEmpty() == false) { + List searchParams = mySearchParamRegistry.getActiveUniqueSearchParams(myResourceName, paramNames); + if (searchParams.size() > 0) { + List> params = new ArrayList<>(); + for (Entry>> nextParamNameToValues : theParams.entrySet()) { + String nextParamName = nextParamNameToValues.getKey(); + nextParamName = UrlUtil.escape(nextParamName); + for (List nextAnd : nextParamNameToValues.getValue()) { + ArrayList nextValueList = new ArrayList<>(); + params.add(nextValueList); + for (IQueryParameterType nextOr : nextAnd) { + String nextOrValue = nextOr.getValueAsQueryToken(myContext); + nextOrValue = UrlUtil.escape(nextOrValue); + nextValueList.add(nextParamName + "=" + nextOrValue); + } } } + + Set uniqueQueryStrings = BaseHapiFhirDao.extractCompositeStringUniquesValueChains(myResourceName, params); + ourLastHandlerMechanismForUnitTest = HandlerTypeEnum.UNIQUE_INDEX; + return new UniqueIndexIterator(uniqueQueryStrings); + } - - Set uniqueQueryStrings = BaseHapiFhirDao.extractCompositeStringUniquesValueChains(myResourceName, params); - ourLastHandlerMechanismForUnitTest = HandlerTypeEnum.UNIQUE_INDEX; - return new UniqueIndexIterator(uniqueQueryStrings); - } } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java index 01b06e06048..911ec59a14a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java @@ -58,7 +58,7 @@ public class FhirResourceDaoSearchParameterDstu3 extends FhirResourceDaoDstu3 fetchAllConformanceResources(FhirContext theContext) { + return null; + } + + @Override + @Transactional(value = TxType.SUPPORTS) + public List fetchAllStructureDefinitions(FhirContext theContext) { + return Collections.emptyList(); + } + @Override public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) { return fetchResource(theCtx, CodeSystem.class, theSystem); @@ -103,7 +111,7 @@ public class JpaValidationSupportDstu3 implements IJpaValidationSupportDstu3 { params = new SearchParameterMap(); params.setLoadSynchronousUpTo(1); params.add(ValueSet.SP_URL, new UriParam(theUri)); - search = myValueSetDao.search(params); + search = myValueSetDao.search(params); } } else { SearchParameterMap params = new SearchParameterMap(); @@ -145,28 +153,20 @@ public class JpaValidationSupportDstu3 implements IJpaValidationSupportDstu3 { } @Override - @Transactional(value=TxType.SUPPORTS) + public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { + return fetchResource(theCtx, StructureDefinition.class, theUrl); + } + + @Override + @Transactional(value = TxType.SUPPORTS) public boolean isCodeSystemSupported(FhirContext theCtx, String theSystem) { return false; } @Override - @Transactional(value=TxType.SUPPORTS) + @Transactional(value = TxType.SUPPORTS) public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay) { return null; } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - return fetchResource(theCtx, StructureDefinition.class, theUrl); - } - - - @Override - @Transactional(value=TxType.SUPPORTS) - public List fetchAllStructureDefinitions(FhirContext theContext) { - return Collections.emptyList(); - } - } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4.java index d76ea7a5140..20070c88268 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4.java @@ -60,7 +60,7 @@ public class FhirResourceDaoSearchParameterR4 extends FhirResourceDaoR4 fetchAllConformanceResources(FhirContext theContext) { + return null; + } + + @Override + @Transactional(value = TxType.SUPPORTS) + public List fetchAllStructureDefinitions(FhirContext theContext) { + return Collections.emptyList(); + } + @Override public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) { return fetchResource(theCtx, CodeSystem.class, theSystem); @@ -103,7 +111,7 @@ public class JpaValidationSupportR4 implements IJpaValidationSupportR4 { params = new SearchParameterMap(); params.setLoadSynchronousUpTo(1); params.add(ValueSet.SP_URL, new UriParam(theUri)); - search = myValueSetDao.search(params); + search = myValueSetDao.search(params); } } else { SearchParameterMap params = new SearchParameterMap(); @@ -145,28 +153,20 @@ public class JpaValidationSupportR4 implements IJpaValidationSupportR4 { } @Override - @Transactional(value=TxType.SUPPORTS) + public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { + return fetchResource(theCtx, StructureDefinition.class, theUrl); + } + + @Override + @Transactional(value = TxType.SUPPORTS) public boolean isCodeSystemSupported(FhirContext theCtx, String theSystem) { return false; } @Override - @Transactional(value=TxType.SUPPORTS) + @Transactional(value = TxType.SUPPORTS) public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay) { return null; } - - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - return fetchResource(theCtx, StructureDefinition.class, theUrl); - } - - - @Override - @Transactional(value=TxType.SUPPORTS) - public List fetchAllStructureDefinitions(FhirContext theContext) { - return Collections.emptyList(); - } - } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedCompositeStringUnique.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedCompositeStringUnique.java index 3f7c7f4a0a7..3a44fafebfd 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedCompositeStringUnique.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedCompositeStringUnique.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.entity; +import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.*; import org.hl7.fhir.r4.model.Resource; @@ -88,6 +89,7 @@ public class ResourceIndexedCompositeStringUnique implements Comparable extends BaseJpaProvider { public static final String MARK_ALL_RESOURCES_FOR_REINDEXING = "$mark-all-resources-for-reindexing"; + public static final String PERFORM_REINDEXING_PASS = "$perform-reindexing-pass"; private IFhirSystemDao myDao; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaSystemProviderDstu2Plus.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaSystemProviderDstu2Plus.java index 37897210d11..e8ad50fbc6f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaSystemProviderDstu2Plus.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaSystemProviderDstu2Plus.java @@ -30,11 +30,9 @@ import ca.uhn.fhir.util.ParametersUtil; public abstract class BaseJpaSystemProviderDstu2Plus extends BaseJpaSystemProvider { - //@formatter:off @Operation(name=MARK_ALL_RESOURCES_FOR_REINDEXING, idempotent=true, returnParameters= { @OperationParam(name="status") }) - //@formatter:on public IBaseResource markAllResourcesForReindexing() { int count = getDao().markAllResourcesForReindexing(); @@ -42,9 +40,22 @@ public abstract class BaseJpaSystemProviderDstu2Plus extends BaseJpaSyste IPrimitiveType string = ParametersUtil.createString(getContext(), "Marked " + count + " resources"); ParametersUtil.addParameterToParameters(getContext(), retVal, string, "status"); - + + return retVal; + } + + @Operation(name=PERFORM_REINDEXING_PASS, idempotent=true, returnParameters= { + @OperationParam(name="status") + }) + public IBaseResource performReindexingPass() { + int count = getDao().performReindexingPass(1000); + + IBaseParameters retVal = ParametersUtil.newInstance(getContext()); + + IPrimitiveType string = ParametersUtil.createString(getContext(), "Indexed " + count + " resources"); + ParametersUtil.addParameterToParameters(getContext(), retVal, string, "status"); + return retVal; } - } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java index 57cbbec2c73..a8ac39a2600 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java @@ -254,6 +254,11 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvc implements I return retVal; } + @Override + public List fetchAllConformanceResources(FhirContext theContext) { + return null; + } + @Override public List expandValueSet(String theValueSet) { ValueSet source = new ValueSet(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java index 990d7d60750..45189318700 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java @@ -1,5 +1,43 @@ package ca.uhn.fhir.jpa.term; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.DaoMethodOutcome; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.jpa.entity.TermCodeSystem; +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.util.StopWatch; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.util.CoverageIgnore; +import ca.uhn.fhir.util.UrlUtil; +import org.apache.lucene.search.Query; +import org.hibernate.search.jpa.FullTextEntityManager; +import org.hibernate.search.jpa.FullTextQuery; +import org.hibernate.search.query.dsl.BooleanJunction; +import org.hibernate.search.query.dsl.QueryBuilder; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; +import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode; +import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; +import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.ValueSet; +import org.hl7.fhir.r4.model.ValueSet.*; +import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; +import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; + import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -12,9 +50,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,36 +60,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * limitations under the License. * #L% */ -import java.util.*; - -import org.apache.lucene.search.Query; -import org.hibernate.search.jpa.FullTextEntityManager; -import org.hibernate.search.jpa.FullTextQuery; -import org.hibernate.search.query.dsl.BooleanJunction; -import org.hibernate.search.query.dsl.QueryBuilder; -import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.model.*; -import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode; -import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; -import org.hl7.fhir.r4.model.ValueSet.*; -import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.*; -import ca.uhn.fhir.jpa.entity.*; -import ca.uhn.fhir.jpa.util.StopWatch; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.exceptions.*; -import ca.uhn.fhir.util.CoverageIgnore; -import ca.uhn.fhir.util.UrlUtil; public class HapiTerminologySvcR4 extends BaseHapiTerminologySvc implements IValidationSupport, IHapiTerminologySvcR4 { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(HapiTerminologySvcR4.class); @@ -63,6 +71,15 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvc implements IVal @Autowired private IValidationSupport myValidationSupport; + private void addAllChildren(String theSystemString, ConceptDefinitionComponent theCode, List theListToPopulate) { + if (isNotBlank(theCode.getCode())) { + theListToPopulate.add(new VersionIndependentConcept(theSystemString, theCode.getCode())); + } + for (ConceptDefinitionComponent nextChild : theCode.getConcept()) { + addAllChildren(theSystemString, nextChild, theListToPopulate); + } + } + private void addCodeIfNotAlreadyAdded(String system, ValueSetExpansionComponent retVal, Set addedCodes, TermConcept nextConcept) { if (addedCodes.add(nextConcept.getCode())) { ValueSetExpansionContainsComponent contains = retVal.addContains(); @@ -72,36 +89,20 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvc implements IVal } } - @Override - protected List findCodesBelowUsingBuiltInSystems(String theSystem, String theCode) { - ArrayList retVal = new ArrayList(); - CodeSystem system = myValidationSupport.fetchCodeSystem(myContext, theSystem); - if (system != null) { - findCodesBelow(system, theSystem, theCode, retVal); - } - return retVal; + private void addDisplayFilterExact(QueryBuilder qb, BooleanJunction bool, ConceptSetFilterComponent nextFilter) { + bool.must(qb.phrase().onField("myDisplay").sentence(nextFilter.getValue()).createQuery()); } - private void findCodesBelow(CodeSystem theSystem, String theSystemString, String theCode, List theListToPopulate) { - List conceptList = theSystem.getConcept(); - findCodesBelow(theSystemString, theCode, theListToPopulate, conceptList); - } - - private void findCodesBelow(String theSystemString, String theCode, List theListToPopulate, List conceptList) { - for (ConceptDefinitionComponent next : conceptList) { - if (theCode.equals(next.getCode())) { - addAllChildren(theSystemString, next, theListToPopulate); - } else { - findCodesBelow(theSystemString, theCode, theListToPopulate, next.getConcept()); - } - } - } - - private void findCodesAbove(CodeSystem theSystem, String theSystemString, String theCode, List theListToPopulate) { - List conceptList = theSystem.getConcept(); - for (ConceptDefinitionComponent next : conceptList) { - addTreeIfItContainsCode(theSystemString, next, theCode, theListToPopulate); - } + private void addDisplayFilterInexact(QueryBuilder qb, BooleanJunction bool, ConceptSetFilterComponent nextFilter) { + Query textQuery = qb + .phrase() + .withSlop(2) + .onField("myDisplay").boostedTo(4.0f) + .andField("myDisplayEdgeNGram").boostedTo(2.0f) + // .andField("myDisplayNGram").boostedTo(1.0f) + // .andField("myDisplayPhonetic").boostedTo(0.5f) + .sentence(nextFilter.getValue().toLowerCase()).createQuery(); + bool.must(textQuery); } private boolean addTreeIfItContainsCode(String theSystemString, ConceptDefinitionComponent theNext, String theCode, List theListToPopulate) { @@ -118,41 +119,6 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvc implements IVal return false; } - private void addAllChildren(String theSystemString, ConceptDefinitionComponent theCode, List theListToPopulate) { - if (isNotBlank(theCode.getCode())) { - theListToPopulate.add(new VersionIndependentConcept(theSystemString, theCode.getCode())); - } - for (ConceptDefinitionComponent nextChild : theCode.getConcept()) { - addAllChildren(theSystemString, nextChild, theListToPopulate); - } - } - - @Override - protected List findCodesAboveUsingBuiltInSystems(String theSystem, String theCode) { - ArrayList retVal = new ArrayList(); - CodeSystem system = myValidationSupport.fetchCodeSystem(myContext, theSystem); - if (system != null) { - findCodesAbove(system, theSystem, theCode, retVal); - } - return retVal; - } - - private void addDisplayFilterExact(QueryBuilder qb, BooleanJunction bool, ConceptSetFilterComponent nextFilter) { - bool.must(qb.phrase().onField("myDisplay").sentence(nextFilter.getValue()).createQuery()); - } - - private void addDisplayFilterInexact(QueryBuilder qb, BooleanJunction bool, ConceptSetFilterComponent nextFilter) { - Query textQuery = qb - .phrase() - .withSlop(2) - .onField("myDisplay").boostedTo(4.0f) - .andField("myDisplayEdgeNGram").boostedTo(2.0f) - // .andField("myDisplayNGram").boostedTo(1.0f) - // .andField("myDisplayPhonetic").boostedTo(0.5f) - .sentence(nextFilter.getValue().toLowerCase()).createQuery(); - bool.must(textQuery); - } - @Override public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { String system = theInclude.getSystem(); @@ -204,8 +170,8 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvc implements IVal if (isBlank(nextFilter.getValue()) || nextFilter.getOp() == null || isBlank(nextFilter.getProperty())) { throw new InvalidRequestException("Invalid filter, must have fields populated: property op value"); } - - + + if (nextFilter.getProperty().equals("display:exact") && nextFilter.getOp() == FilterOperator.EQUAL) { addDisplayFilterExact(qb, bool, nextFilter); } else if ("display".equals(nextFilter.getProperty()) && nextFilter.getOp() == FilterOperator.EQUAL) { @@ -232,12 +198,12 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvc implements IVal jpaQuery.setMaxResults(1000); StopWatch sw = new StopWatch(); - + @SuppressWarnings("unchecked") List result = jpaQuery.getResultList(); - + ourLog.info("Expansion completed in {}ms", sw.getMillis()); - + for (TermConcept nextConcept : result) { addCodeIfNotAlreadyAdded(system, retVal, addedCodes, nextConcept); } @@ -278,6 +244,11 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvc implements IVal } + @Override + public List fetchAllConformanceResources(FhirContext theContext) { + return null; + } + @Override public List fetchAllStructureDefinitions(FhirContext theContext) { return Collections.emptyList(); @@ -300,6 +271,48 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvc implements IVal return null; } + private void findCodesAbove(CodeSystem theSystem, String theSystemString, String theCode, List theListToPopulate) { + List conceptList = theSystem.getConcept(); + for (ConceptDefinitionComponent next : conceptList) { + addTreeIfItContainsCode(theSystemString, next, theCode, theListToPopulate); + } + } + + @Override + protected List findCodesAboveUsingBuiltInSystems(String theSystem, String theCode) { + ArrayList retVal = new ArrayList(); + CodeSystem system = myValidationSupport.fetchCodeSystem(myContext, theSystem); + if (system != null) { + findCodesAbove(system, theSystem, theCode, retVal); + } + return retVal; + } + + private void findCodesBelow(CodeSystem theSystem, String theSystemString, String theCode, List theListToPopulate) { + List conceptList = theSystem.getConcept(); + findCodesBelow(theSystemString, theCode, theListToPopulate, conceptList); + } + + private void findCodesBelow(String theSystemString, String theCode, List theListToPopulate, List conceptList) { + for (ConceptDefinitionComponent next : conceptList) { + if (theCode.equals(next.getCode())) { + addAllChildren(theSystemString, next, theListToPopulate); + } else { + findCodesBelow(theSystemString, theCode, theListToPopulate, next.getConcept()); + } + } + } + + @Override + protected List findCodesBelowUsingBuiltInSystems(String theSystem, String theCode) { + ArrayList retVal = new ArrayList(); + CodeSystem system = myValidationSupport.fetchCodeSystem(myContext, theSystem); + if (system != null) { + findCodesBelow(system, theSystem, theCode, retVal); + } + return retVal; + } + @Override public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { return super.supportsSystem(theSystem); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/StopWatch.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/StopWatch.java index f1389e3d39c..414de7e53be 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/StopWatch.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/StopWatch.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.jpa.util; +import org.apache.commons.lang3.time.DateUtils; + import java.util.Date; /* @@ -72,9 +74,15 @@ public class StopWatch { static public String formatMillis(long val) { StringBuilder buf = new StringBuilder(20); - append(buf, "", 2, ((val % 3600000) / 60000)); - append(buf, ":", 2, ((val % 60000) / 1000)); - append(buf, ".", 3, (val % 1000)); + if (val >= DateUtils.MILLIS_PER_DAY) { + append(buf, "", 1, ((val / DateUtils.MILLIS_PER_DAY))); + append(buf, "d", 2, ((val % DateUtils.MILLIS_PER_DAY) / DateUtils.MILLIS_PER_HOUR)); + } else { + append(buf, "", 2, ((val % DateUtils.MILLIS_PER_DAY) / DateUtils.MILLIS_PER_HOUR)); + } + append(buf, ":", 2, ((val % DateUtils.MILLIS_PER_HOUR) / DateUtils.MILLIS_PER_MINUTE)); + append(buf, ":", 2, ((val % DateUtils.MILLIS_PER_MINUTE) / DateUtils.MILLIS_PER_SECOND)); + append(buf, ".", 3, (val % DateUtils.MILLIS_PER_SECOND)); return buf.toString(); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java index 99e4eb030d8..0e3af0f029f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java @@ -35,6 +35,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { @After public void after() { myDaoConfig.setDefaultSearchParamsCanBeOverridden(new DaoConfig().isDefaultSearchParamsCanBeOverridden()); + myDaoConfig.setUniqueIndexesCheckedBeforeSave(new DaoConfig().isUniqueIndexesCheckedBeforeSave()); } @Before @@ -118,6 +119,58 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { mySearchParamRegsitry.forceRefresh(); } + private void createUniqueObservationSubjectDateCode() { + SearchParameter sp = new SearchParameter(); + sp.setId("SearchParameter/obs-subject"); + sp.setType(Enumerations.SearchParamType.REFERENCE); + sp.setCode("subject"); + sp.setExpression("Observation.subject"); + sp.setStatus(PublicationStatus.ACTIVE); + sp.addBase("Observation"); + sp.addTarget("Patient"); + mySearchParameterDao.update(sp); + + sp = new SearchParameter(); + sp.setId("SearchParameter/obs-effective"); + sp.setType(Enumerations.SearchParamType.DATE); + sp.setCode("date"); + sp.setExpression("Observation.effective"); + sp.setStatus(PublicationStatus.ACTIVE); + sp.addBase("Observation"); + mySearchParameterDao.update(sp); + + sp = new SearchParameter(); + sp.setId("SearchParameter/obs-code"); + sp.setType(Enumerations.SearchParamType.TOKEN); + sp.setCode("code"); + sp.setExpression("Observation.code"); + sp.setStatus(PublicationStatus.ACTIVE); + sp.addBase("Observation"); + mySearchParameterDao.update(sp); + + sp = new SearchParameter(); + sp.setId("SearchParameter/observation-subject-date-code"); + sp.setType(Enumerations.SearchParamType.COMPOSITE); + sp.setStatus(PublicationStatus.ACTIVE); + sp.addBase("Observation"); + sp.setExpression("Observation.code"); + sp.addComponent() + .setExpression("Observation") + .setDefinition(new Reference("SearchParameter/obs-subject")); + sp.addComponent() + .setExpression("Observation") + .setDefinition(new Reference("SearchParameter/obs-effective")); + sp.addComponent() + .setExpression("Observation") + .setDefinition(new Reference("SearchParameter/obs-code")); + sp.addExtension() + .setUrl(JpaConstants.EXT_SP_UNIQUE) + .setValue(new BooleanType(true)); + mySearchParameterDao.update(sp); + + mySearchParamRegsitry.forceRefresh(); + } + @Test public void testDetectUniqueSearchParams() { createUniqueBirthdateAndGenderSps(); @@ -131,9 +184,61 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { assertEquals("gender", params.get(0).getCompositeOf().get(1).getName()); } + @Test + public void testDuplicateUniqueValuesAreReIndexed() { + + Patient pt1 = new Patient(); + pt1.setActive(true); + IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + + /* + * Both of the following resources will match the unique index we'll + * create afterward. So we can create them both, but then when we create + * the unique index that matches them both that's a problem... + */ + + Observation obs = new Observation(); + obs.getCode().addCoding().setSystem("foo").setCode("bar"); + obs.setSubject(new Reference(pt1.getIdElement().toUnqualifiedVersionless().getValue())); + obs.setEffective(new DateTimeType("2011-01-01")); + IIdType id2 = myObservationDao.create(obs).getId().toUnqualifiedVersionless(); + + obs = new Observation(); + obs.getCode().addCoding().setSystem("foo").setCode("bar"); + obs.setSubject(new Reference(pt1.getIdElement().toUnqualifiedVersionless().getValue())); + obs.setEffective(new DateTimeType("2011-01-01")); + IIdType id3 = myObservationDao.create(obs).getId().toUnqualifiedVersionless(); + + ourLog.info("ID1: {} - ID2: {} - ID3: {}", id1, id2, id3); + + createUniqueObservationSubjectDateCode(); + + mySystemDao.markAllResourcesForReindexing(); + mySystemDao.performReindexingPass(1000); + mySystemDao.performReindexingPass(1000); + + List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + assertEquals(uniques.toString(), 1, uniques.size()); + assertEquals("Observation/" + id2.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); + assertEquals("Observation?code=foo%7Cbar&date=2011-01-01&subject=Patient%2F" + id1.getIdPart(), uniques.get(0).getIndexString()); + + myResourceIndexedCompositeStringUniqueDao.deleteAll(); + + mySystemDao.markAllResourcesForReindexing(); + mySystemDao.performReindexingPass(1000); + mySystemDao.performReindexingPass(1000); + + uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + assertEquals(uniques.toString(), 1, uniques.size()); + assertEquals("Observation/" + id2.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); + assertEquals("Observation?code=foo%7Cbar&date=2011-01-01&subject=Patient%2F" + id1.getIdPart(), uniques.get(0).getIndexString()); + + } @Test - public void testDuplicateUniqueValuesAreRejected() { + public void testDuplicateUniqueValuesAreRejectedWithChecking_TestingDisabled() { + myDaoConfig.setUniqueIndexesCheckedBeforeSave(false); + createUniqueBirthdateAndGenderSps(); Patient pt1 = new Patient(); @@ -147,26 +252,10 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { } catch (JpaSystemException e) { // good } - - Patient pt2 = new Patient(); - pt2.setGender(Enumerations.AdministrativeGender.MALE); - IIdType id2 = myPatientDao.create(pt2).getId().toUnqualifiedVersionless(); - - pt2 = new Patient(); - pt2.setId(id2); - pt2.setGender(Enumerations.AdministrativeGender.MALE); - pt2.setBirthDateElement(new DateType("2011-01-01")); - try { - myPatientDao.update(pt2); - fail(); - } catch (JpaSystemException e) { - // good - } - } @Test - public void testUniqueValuesAreIndexed_DateAndToken() { + public void testDuplicateUniqueValuesAreRejectedWithChecking_TestingEnabled() { createUniqueBirthdateAndGenderSps(); Patient pt1 = new Patient(); @@ -174,10 +263,12 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { pt1.setBirthDateElement(new DateType("2011-01-01")); IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); - assertEquals(1, uniques.size()); - assertEquals("Patient/" + id1.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); - assertEquals("Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale", uniques.get(0).getIndexString()); + try { + myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + fail(); + } catch (PreconditionFailedException e) { + assertEquals("Can not create resource of type Patient as it would create a duplicate index matching query: Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale (existing index belongs to Patient/" + id1.getIdPart() + ")", e.getMessage()); + } } @Test @@ -204,7 +295,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { assertEquals(SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest()); } - @Test + @Test public void testSearchUsingUniqueComposite() { createUniqueBirthdateAndGenderSps(); @@ -255,6 +346,51 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { } + @Test + public void testUniqueValuesAreIndexed_DateAndToken() { + createUniqueBirthdateAndGenderSps(); + + Patient pt1 = new Patient(); + pt1.setGender(Enumerations.AdministrativeGender.MALE); + pt1.setBirthDateElement(new DateType("2011-01-01")); + IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + + List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + assertEquals(uniques.toString(), 1, uniques.size()); + assertEquals("Patient/" + id1.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); + assertEquals("Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale", uniques.get(0).getIndexString()); + } + + @Test + public void testUniqueValuesAreIndexed_RefAndDateAndToken() { + createUniqueObservationSubjectDateCode(); + + List uniques; + uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + assertEquals(uniques.toString(), 0, uniques.size()); + + Patient pt1 = new Patient(); + pt1.setActive(true); + IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + + Observation obs = new Observation(); + obs.getCode().addCoding().setSystem("foo").setCode("bar"); + obs.setSubject(new Reference(pt1.getIdElement().toUnqualifiedVersionless().getValue())); + obs.setEffective(new DateTimeType("2011-01-01")); + IIdType id2 = myObservationDao.create(obs).getId().toUnqualifiedVersionless(); + + Patient pt2 = new Patient(); + pt2.setActive(false); + IIdType id3 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + + ourLog.info("ID1: {} - ID2: {} - ID3: {}", id1, id2, id3); + + uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + assertEquals(uniques.toString(), 1, uniques.size()); + assertEquals("Observation/" + id2.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); + assertEquals("Observation?code=foo%7Cbar&date=2011-01-01&subject=Patient%2F" + id1.getIdPart(), uniques.get(0).getIndexString()); + } + @Test public void testUniqueValuesAreIndexed_StringAndReference() { createUniqueNameAndManagingOrganizationSps(); @@ -287,6 +423,106 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { assertEquals("Patient?name=GIVEN2&organization=Organization%2FORG", uniques.get(2).getIndexString()); } + @Test + public void testUniqueValuesAreNotIndexedIfNotAllParamsAreFound_DateAndToken() { + createUniqueBirthdateAndGenderSps(); + + Patient pt; + List uniques; + + pt = new Patient(); + pt.setGender(Enumerations.AdministrativeGender.MALE); + myPatientDao.create(pt).getId().toUnqualifiedVersionless(); + uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + assertEquals(uniques.toString(), 0, uniques.size()); + + pt = new Patient(); + myPatientDao.create(pt).getId().toUnqualifiedVersionless(); + uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + assertEquals(uniques.toString(), 0, uniques.size()); + + pt = new Patient(); + pt.setBirthDateElement(new DateType()); + pt.setGender(Enumerations.AdministrativeGender.MALE); + myPatientDao.create(pt).getId().toUnqualifiedVersionless(); + uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + assertEquals(uniques.toString(), 0, uniques.size()); + + } + + @Test + public void testUniqueValuesAreNotIndexedIfNotAllParamsAreFound_StringAndReference() { + createUniqueNameAndManagingOrganizationSps(); + + Organization org = new Organization(); + org.setId("Organization/ORG"); + org.setName("ORG"); + myOrganizationDao.update(org); + + List uniques; + Patient pt; + + pt = new Patient(); + pt.setManagingOrganization(new Reference("Organization/ORG")); + myPatientDao.create(pt).getId().toUnqualifiedVersionless(); + uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + assertEquals(uniques.toString(), 0, uniques.size()); + + pt = new Patient(); + pt.addName() + .setFamily("FAMILY1") + .addGiven("GIVEN1") + .addGiven("GIVEN2") + .addGiven("GIVEN2"); // GIVEN2 happens twice + myPatientDao.create(pt).getId().toUnqualifiedVersionless(); + uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + assertEquals(uniques.toString(), 0, uniques.size()); + + pt = new Patient(); + pt.setActive(true); + myPatientDao.create(pt).getId().toUnqualifiedVersionless(); + uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + assertEquals(uniques.toString(), 0, uniques.size()); + } + + @Test + public void testUniqueValuesAreReIndexed() { + createUniqueObservationSubjectDateCode(); + + Patient pt1 = new Patient(); + pt1.setActive(true); + IIdType id1 = myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + + Observation obs = new Observation(); + obs.getCode().addCoding().setSystem("foo").setCode("bar"); + obs.setSubject(new Reference(pt1.getIdElement().toUnqualifiedVersionless().getValue())); + obs.setEffective(new DateTimeType("2011-01-01")); + IIdType id2 = myObservationDao.create(obs).getId().toUnqualifiedVersionless(); + + Patient pt2 = new Patient(); + pt2.setActive(false); + myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); + + mySystemDao.markAllResourcesForReindexing(); + mySystemDao.performReindexingPass(1000); + + List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + assertEquals(uniques.toString(), 1, uniques.size()); + assertEquals("Observation/" + id2.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); + assertEquals("Observation?code=foo%7Cbar&date=2011-01-01&subject=Patient%2F" + id1.getIdPart(), uniques.get(0).getIndexString()); + + myResourceIndexedCompositeStringUniqueDao.deleteAll(); + + mySystemDao.markAllResourcesForReindexing(); + mySystemDao.performReindexingPass(1000); + + uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + assertEquals(uniques.toString(), 1, uniques.size()); + assertEquals("Observation/" + id2.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); + assertEquals("Observation?code=foo%7Cbar&date=2011-01-01&subject=Patient%2F" + id1.getIdPart(), uniques.get(0).getIndexString()); + + } + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java index 0a26b2caf46..c00286c6825 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java @@ -362,6 +362,17 @@ public class SystemProviderR4Test extends BaseJpaR4Test { } finally { IOUtils.closeQuietly(http);; } + + get = new HttpGet(ourServerBase + "/$perform-reindexing-pass"); + http = ourHttpClient.execute(get); + try { + String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(output); + assertEquals(200, http.getStatusLine().getStatusCode()); + } finally { + IOUtils.closeQuietly(http);; + } + } @Transactional(propagation = Propagation.NEVER) diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/util/StopWatchTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/util/StopWatchTest.java index 1e1eafe10fb..54a2ed29e27 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/util/StopWatchTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/util/StopWatchTest.java @@ -3,10 +3,12 @@ package ca.uhn.fhir.jpa.util; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import java.util.Date; +import org.apache.commons.lang3.time.DateUtils; import org.junit.Test; public class StopWatchTest { @@ -46,4 +48,14 @@ public class StopWatchTest { assertThat(string, startsWith("00:00")); } + @Test + public void testFormatMillis() throws Exception { + assertEquals("00:00:01.000", StopWatch.formatMillis(DateUtils.MILLIS_PER_SECOND)); + assertEquals("00:01:00.000", StopWatch.formatMillis(DateUtils.MILLIS_PER_MINUTE)); + assertEquals("01:00:00.000", StopWatch.formatMillis(DateUtils.MILLIS_PER_HOUR)); + assertEquals("1d00:00:00.000", StopWatch.formatMillis(DateUtils.MILLIS_PER_DAY)); + assertEquals("2d00:00:00.000", StopWatch.formatMillis(DateUtils.MILLIS_PER_DAY*2)); + assertEquals("2d00:00:00.001", StopWatch.formatMillis((DateUtils.MILLIS_PER_DAY*2)+1)); + } + } diff --git a/hapi-fhir-jpaserver-uhnfhirtest/derby_maintenance.txt b/hapi-fhir-jpaserver-uhnfhirtest/derby_maintenance.txt index 3646c2ac1b7..21e69afe88a 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/derby_maintenance.txt +++ b/hapi-fhir-jpaserver-uhnfhirtest/derby_maintenance.txt @@ -44,12 +44,34 @@ delete from hfj_forced_id where resource_pid = 16940; delete from hfj_resource where res_id = 16940; +# Drop all tables +drop table hfj_history_tag cascade constraints; +drop table hfj_res_ver cascade constraints; +drop table hfj_forced_id cascade constraints; +drop table hfj_res_link cascade constraints; +drop table hfj_res_link cascade constraints; +drop table hfj_spidx_coords cascade constraints; +drop table hfj_spidx_date cascade constraints; +drop table hfj_spidx_number cascade constraints; +drop table hfj_spidx_quantity cascade constraints; +drop table hfj_spidx_string cascade constraints; +drop table hfj_spidx_token cascade constraints; +drop table hfj_spidx_uri cascade constraints; +drop table hfj_res_tag cascade constraints; +drop table hfj_search_result cascade constraints; +drop table hfj_res_param_present cascade constraints; +drop table hfj_resource cascade constraints; +drop table hfj_idx_cmp_string_uniq cascade constraints; +drop table hfj_search cascade constraints; +drop table hfj_search_include cascade constraints; +drop table hfj_search_parm cascade constraints; +drop table hfj_subscription cascade constraints; +drop table hfj_subscription_flag_res cascade constraints; +drop table hfj_tag_def cascade constraints; +drop table trm_codesystem cascade constraints; +drop table trm_codesystem_var cascade constraints; +drop table trm_concept cascade constraints; +drop table trm_concept_pc_link cascade constraints; +drop table trm_concept_property cascade constraints; -delete from hfj_res_link where src_resource_id in ( 156618, 1092 , 1114 , 1139 , 1140 , 1141, 1142 , 1143 , 1144 , 1146 , 1147, 1148, 1062912, 1062916, 1062918, 1062919, 1062922, 1062929, 1062930, 1062934, 1062939, 1062940, 1062944, 1062949, 1062955, 1062957, 1062958, 1062959, 1062966, 1062969, 1062975, 1062976, 1062979, 1062981, 1062985, 1062987, 1062992, 1063002, 1063005, 1063007, 1063013, 1063016, 1063018, 1063020, 1063022, 1063062, 1063068, 1063075, 1063078, 1063080, 1063083, 1063084, 1063091, 1063095, 1063096, 1063098, 1107579, 1107591, 1107598, 1107761, 1107705, 1107748, 1109361, 1109388, 1109378, 1109399, 1109400, 1109401, 1109403, 1109404, 1109406, 1109409, 1109411, 1109414, 1109417, 1109418, 1109421, 1109427, 1109428, 1109429, 1109431, 1109432, 1109433, 1109891, 1109893, 1109947, 1179553, 1182781, 1182788, 1182791, 1182792, 1182795, 1182798, 1182801, 1182806, 1182811, 1182815) or target_resource_id in ( 156618, 1092 , 1114 , 1139 , 1140 , 1141, 1142 , 1143 , 1144 , 1146 , 1147, 1148, 1062912, 1062916, 1062918, 1062919, 1062922, 1062929, 1062930, 1062934, 1062939, 1062940, 1062944, 1062949, 1062955, 1062957, 1062958, 1062959, 1062966, 1062969, 1062975, 1062976, 1062979, 1062981, 1062985, 1062987, 1062992, 1063002, 1063005, 1063007, 1063013, 1063016, 1063018, 1063020, 1063022, 1063062, 1063068, 1063075, 1063078, 1063080, 1063083, 1063084, 1063091, 1063095, 1063096, 1063098, 1107579, 1107591, 1107598, 1107761, 1107705, 1107748, 1109361, 1109388, 1109378, 1109399, 1109400, 1109401, 1109403, 1109404, 1109406, 1109409, 1109411, 1109414, 1109417, 1109418, 1109421, 1109427, 1109428, 1109429, 1109431, 1109432, 1109433, 1109891, 1109893, 1109947, 1179553, 1182781, 1182788, 1182791, 1182792, 1182795, 1182798, 1182801, 1182806, 1182811, 1182815); -delete from hfj_spidx_date where res_id in ( 156618, 1092 , 1114 , 1139 , 1140 , 1141, 1142 , 1143 , 1144 , 1146 , 1147, 1148, 1062912, 1062916, 1062918, 1062919, 1062922, 1062929, 1062930, 1062934, 1062939, 1062940, 1062944, 1062949, 1062955, 1062957, 1062958, 1062959, 1062966, 1062969, 1062975, 1062976, 1062979, 1062981, 1062985, 1062987, 1062992, 1063002, 1063005, 1063007, 1063013, 1063016, 1063018, 1063020, 1063022, 1063062, 1063068, 1063075, 1063078, 1063080, 1063083, 1063084, 1063091, 1063095, 1063096, 1063098, 1107579, 1107591, 1107598, 1107761, 1107705, 1107748, 1109361, 1109388, 1109378, 1109399, 1109400, 1109401, 1109403, 1109404, 1109406, 1109409, 1109411, 1109414, 1109417, 1109418, 1109421, 1109427, 1109428, 1109429, 1109431, 1109432, 1109433, 1109891, 1109893, 1109947, 1179553, 1182781, 1182788, 1182791, 1182792, 1182795, 1182798, 1182801, 1182806, 1182811, 1182815); -delete from hfj_spidx_string where res_id in ( 156618, 1092 , 1114 , 1139 , 1140 , 1141, 1142 , 1143 , 1144 , 1146 , 1147, 1148, 1062912, 1062916, 1062918, 1062919, 1062922, 1062929, 1062930, 1062934, 1062939, 1062940, 1062944, 1062949, 1062955, 1062957, 1062958, 1062959, 1062966, 1062969, 1062975, 1062976, 1062979, 1062981, 1062985, 1062987, 1062992, 1063002, 1063005, 1063007, 1063013, 1063016, 1063018, 1063020, 1063022, 1063062, 1063068, 1063075, 1063078, 1063080, 1063083, 1063084, 1063091, 1063095, 1063096, 1063098, 1107579, 1107591, 1107598, 1107761, 1107705, 1107748, 1109361, 1109388, 1109378, 1109399, 1109400, 1109401, 1109403, 1109404, 1109406, 1109409, 1109411, 1109414, 1109417, 1109418, 1109421, 1109427, 1109428, 1109429, 1109431, 1109432, 1109433, 1109891, 1109893, 1109947, 1179553, 1182781, 1182788, 1182791, 1182792, 1182795, 1182798, 1182801, 1182806, 1182811, 1182815); -delete from hfj_spidx_token where res_id in ( 156618, 1092 , 1114 , 1139 , 1140 , 1141, 1142 , 1143 , 1144 , 1146 , 1147, 1148, 1062912, 1062916, 1062918, 1062919, 1062922, 1062929, 1062930, 1062934, 1062939, 1062940, 1062944, 1062949, 1062955, 1062957, 1062958, 1062959, 1062966, 1062969, 1062975, 1062976, 1062979, 1062981, 1062985, 1062987, 1062992, 1063002, 1063005, 1063007, 1063013, 1063016, 1063018, 1063020, 1063022, 1063062, 1063068, 1063075, 1063078, 1063080, 1063083, 1063084, 1063091, 1063095, 1063096, 1063098, 1107579, 1107591, 1107598, 1107761, 1107705, 1107748, 1109361, 1109388, 1109378, 1109399, 1109400, 1109401, 1109403, 1109404, 1109406, 1109409, 1109411, 1109414, 1109417, 1109418, 1109421, 1109427, 1109428, 1109429, 1109431, 1109432, 1109433, 1109891, 1109893, 1109947, 1179553, 1182781, 1182788, 1182791, 1182792, 1182795, 1182798, 1182801, 1182806, 1182811, 1182815); -delete from hfj_search_result where resource_pid in ( 156618, 1092 , 1114 , 1139 , 1140 , 1141, 1142 , 1143 , 1144 , 1146 , 1147, 1148, 1062912, 1062916, 1062918, 1062919, 1062922, 1062929, 1062930, 1062934, 1062939, 1062940, 1062944, 1062949, 1062955, 1062957, 1062958, 1062959, 1062966, 1062969, 1062975, 1062976, 1062979, 1062981, 1062985, 1062987, 1062992, 1063002, 1063005, 1063007, 1063013, 1063016, 1063018, 1063020, 1063022, 1063062, 1063068, 1063075, 1063078, 1063080, 1063083, 1063084, 1063091, 1063095, 1063096, 1063098, 1107579, 1107591, 1107598, 1107761, 1107705, 1107748, 1109361, 1109388, 1109378, 1109399, 1109400, 1109401, 1109403, 1109404, 1109406, 1109409, 1109411, 1109414, 1109417, 1109418, 1109421, 1109427, 1109428, 1109429, 1109431, 1109432, 1109433, 1109891, 1109893, 1109947, 1179553, 1182781, 1182788, 1182791, 1182792, 1182795, 1182798, 1182801, 1182806, 1182811, 1182815); -delete from hfj_resource where res_id in ( 156618, 1092 , 1114 , 1139 , 1140 , 1141, 1142 , 1143 , 1144 , 1146 , 1147, 1148, 1062912, 1062916, 1062918, 1062919, 1062922, 1062929, 1062930, 1062934, 1062939, 1062940, 1062944, 1062949, 1062955, 1062957, 1062958, 1062959, 1062966, 1062969, 1062975, 1062976, 1062979, 1062981, 1062985, 1062987, 1062992, 1063002, 1063005, 1063007, 1063013, 1063016, 1063018, 1063020, 1063022, 1063062, 1063068, 1063075, 1063078, 1063080, 1063083, 1063084, 1063091, 1063095, 1063096, 1063098, 1107579, 1107591, 1107598, 1107761, 1107705, 1107748, 1109361, 1109388, 1109378, 1109399, 1109400, 1109401, 1109403, 1109404, 1109406, 1109409, 1109411, 1109414, 1109417, 1109418, 1109421, 1109427, 1109428, 1109429, 1109431, 1109432, 1109433, 1109891, 1109893, 1109947, 1179553, 1182781, 1182788, 1182791, 1182792, 1182795, 1182798, 1182801, 1182806, 1182811, 1182815); - diff --git a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/DefaultProfileValidationSupport.java b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/DefaultProfileValidationSupport.java index 80e48d618cf..b5fe1903fb6 100644 --- a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/DefaultProfileValidationSupport.java +++ b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/DefaultProfileValidationSupport.java @@ -1,32 +1,22 @@ package org.hl7.fhir.dstu2016may.hapi.validation; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - +import ca.uhn.fhir.context.FhirContext; import org.apache.commons.io.Charsets; import org.apache.commons.lang3.Validate; -import org.hl7.fhir.dstu2016may.model.Bundle; +import org.hl7.fhir.dstu2016may.model.*; import org.hl7.fhir.dstu2016may.model.Bundle.BundleEntryComponent; -import org.hl7.fhir.dstu2016may.model.CodeSystem; import org.hl7.fhir.dstu2016may.model.CodeSystem.ConceptDefinitionComponent; -import org.hl7.fhir.dstu2016may.model.DomainResource; import org.hl7.fhir.dstu2016may.model.OperationOutcome.IssueSeverity; -import org.hl7.fhir.dstu2016may.model.StructureDefinition; -import org.hl7.fhir.dstu2016may.model.ValueSet; import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptReferenceComponent; import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.instance.model.api.IBaseResource; -import ca.uhn.fhir.context.FhirContext; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.*; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; public class DefaultProfileValidationSupport implements IValidationSupport { @@ -55,6 +45,15 @@ public class DefaultProfileValidationSupport implements IValidationSupport { return retVal; } + @Override + public List fetchAllConformanceResources(FhirContext theContext) { + ArrayList retVal = new ArrayList<>(); + retVal.addAll(myCodeSystems.values()); + retVal.addAll(myStructureDefinitions.values()); + retVal.addAll(myValueSets.values()); + return retVal; + } + @Override public List fetchAllStructureDefinitions(FhirContext theContext) { return new ArrayList(provideStructureDefinitionMap(theContext).values()); @@ -219,25 +218,6 @@ public class DefaultProfileValidationSupport implements IValidationSupport { return structureDefinitions; } - @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { - CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem); - if (cs != null) { - boolean caseSensitive = true; - if (cs.hasCaseSensitive()) { - caseSensitive = cs.getCaseSensitive(); - } - - CodeValidationResult retVal = testIfConceptIsInList(theCode, cs.getConcept(), caseSensitive); - - if (retVal != null) { - return retVal; - } - } - - return new CodeValidationResult(IssueSeverity.WARNING, "Unknown code: " + theCodeSystem + " / " + theCode); - } - private CodeValidationResult testIfConceptIsInList(String theCode, List conceptList, boolean theCaseSensitive) { String code = theCode; if (theCaseSensitive == false) { @@ -269,4 +249,23 @@ public class DefaultProfileValidationSupport implements IValidationSupport { return retVal; } + @Override + public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { + CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem); + if (cs != null) { + boolean caseSensitive = true; + if (cs.hasCaseSensitive()) { + caseSensitive = cs.getCaseSensitive(); + } + + CodeValidationResult retVal = testIfConceptIsInList(theCode, cs.getConcept(), caseSensitive); + + if (retVal != null) { + return retVal; + } + } + + return new CodeValidationResult(IssueSeverity.WARNING, "Unknown code: " + theCodeSystem + " / " + theCode); + } + } diff --git a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/PrePopulatedValidationSupport.java b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/PrePopulatedValidationSupport.java index f8cfa70d28e..8771c1609ef 100644 --- a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/PrePopulatedValidationSupport.java +++ b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/PrePopulatedValidationSupport.java @@ -1,10 +1,6 @@ package org.hl7.fhir.dstu2016may.hapi.validation; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import ca.uhn.fhir.context.FhirContext; import org.apache.commons.lang3.Validate; import org.hl7.fhir.dstu2016may.model.CodeSystem; import org.hl7.fhir.dstu2016may.model.StructureDefinition; @@ -13,7 +9,10 @@ import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.instance.model.api.IBaseResource; -import ca.uhn.fhir.context.FhirContext; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * This class is an implementation of {@link IValidationSupport} which may be pre-populated @@ -29,30 +28,25 @@ public class PrePopulatedValidationSupport implements IValidationSupport { * Constructor */ public PrePopulatedValidationSupport() { - myStructureDefinitions = new HashMap(); - myValueSets = new HashMap(); - myCodeSystems = new HashMap(); + myStructureDefinitions = new HashMap(); + myValueSets = new HashMap(); + myCodeSystems = new HashMap(); } - - + /** - * Add a new StructureDefinition resource which will be available to the validator. Note that - * {@link StructureDefinition#getUrl() the URL field) in this resource must contain a value as this - * value will be used as the logical URL. + * Constructor + * + * @param theStructureDefinitions The StructureDefinitions to be returned by this module. Keys are the logical URL for the resource, and + * values are the resource itself. + * @param theValueSets The ValueSets to be returned by this module. Keys are the logical URL for the resource, and values are + * the resource itself. + * @param theCodeSystems The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are + * the resource itself. */ - public void addStructureDefinition(StructureDefinition theStructureDefinition) { - Validate.notBlank(theStructureDefinition.getUrl(), "theStructureDefinition.getUrl() must not return a value"); - myStructureDefinitions.put(theStructureDefinition.getUrl(), theStructureDefinition); - } - - /** - * Add a new ValueSet resource which will be available to the validator. Note that - * {@link ValueSet#getUrl() the URL field) in this resource must contain a value as this - * value will be used as the logical URL. - */ - public void addValueSet(ValueSet theValueSet) { - Validate.notBlank(theValueSet.getUrl(), "theValueSet.getUrl() must not return a value"); - myValueSets.put(theValueSet.getUrl(), theValueSet); + public PrePopulatedValidationSupport(Map theStructureDefinitions, Map theValueSets, Map theCodeSystems) { + myStructureDefinitions = theStructureDefinitions; + myValueSets = theValueSets; + myCodeSystems = theCodeSystems; } /** @@ -66,22 +60,23 @@ public class PrePopulatedValidationSupport implements IValidationSupport { } /** - * Constructor - * - * @param theStructureDefinitions - * The StructureDefinitions to be returned by this module. Keys are the logical URL for the resource, and - * values are the resource itself. - * @param theValueSets - * The ValueSets to be returned by this module. Keys are the logical URL for the resource, and values are - * the resource itself. - * @param theCodeSystems - * The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are - * the resource itself. + * Add a new StructureDefinition resource which will be available to the validator. Note that + * {@link StructureDefinition#getUrl() the URL field) in this resource must contain a value as this + * value will be used as the logical URL. */ - public PrePopulatedValidationSupport(Map theStructureDefinitions, Map theValueSets, Map theCodeSystems) { - myStructureDefinitions = theStructureDefinitions; - myValueSets = theValueSets; - myCodeSystems = theCodeSystems; + public void addStructureDefinition(StructureDefinition theStructureDefinition) { + Validate.notBlank(theStructureDefinition.getUrl(), "theStructureDefinition.getUrl() must not return a value"); + myStructureDefinitions.put(theStructureDefinition.getUrl(), theStructureDefinition); + } + + /** + * Add a new ValueSet resource which will be available to the validator. Note that + * {@link ValueSet#getUrl() the URL field) in this resource must contain a value as this + * value will be used as the logical URL. + */ + public void addValueSet(ValueSet theValueSet) { + Validate.notBlank(theValueSet.getUrl(), "theValueSet.getUrl() must not return a value"); + myValueSets.put(theValueSet.getUrl(), theValueSet); } @Override @@ -89,6 +84,15 @@ public class PrePopulatedValidationSupport implements IValidationSupport { return null; } + @Override + public List fetchAllConformanceResources(FhirContext theContext) { + ArrayList retVal = new ArrayList<>(); + retVal.addAll(myCodeSystems.values()); + retVal.addAll(myStructureDefinitions.values()); + retVal.addAll(myValueSets.values()); + return retVal; + } + @Override public List fetchAllStructureDefinitions(FhirContext theContext) { return new ArrayList(myStructureDefinitions.values()); @@ -129,4 +133,4 @@ public class PrePopulatedValidationSupport implements IValidationSupport { return null; } -} \ No newline at end of file +} diff --git a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/ValidationSupportChain.java b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/ValidationSupportChain.java index 409e1d0b1e4..11889dba553 100644 --- a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/ValidationSupportChain.java +++ b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/ValidationSupportChain.java @@ -1,19 +1,18 @@ package org.hl7.fhir.dstu2016may.hapi.validation; -import static org.apache.commons.lang3.StringUtils.isBlank; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - +import ca.uhn.fhir.context.FhirContext; import org.hl7.fhir.dstu2016may.model.CodeSystem; import org.hl7.fhir.dstu2016may.model.StructureDefinition; import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.instance.model.api.IBaseResource; -import ca.uhn.fhir.context.FhirContext; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.apache.commons.lang3.StringUtils.isBlank; public class ValidationSupportChain implements IValidationSupport { @@ -52,6 +51,32 @@ public class ValidationSupportChain implements IValidationSupport { return myChain.get(0).expandValueSet(theCtx, theInclude); } + @Override + public List fetchAllConformanceResources(FhirContext theContext) { + List retVal = new ArrayList<>(); + for (IValidationSupport next : myChain) { + List candidates = next.fetchAllConformanceResources(theContext); + if (candidates != null) { + retVal.addAll(candidates); + } + } + return retVal; + } + + @Override + public List fetchAllStructureDefinitions(FhirContext theContext) { + ArrayList retVal = new ArrayList(); + Set urls = new HashSet(); + for (IValidationSupport nextSupport : myChain) { + for (StructureDefinition next : nextSupport.fetchAllStructureDefinitions(theContext)) { + if (isBlank(next.getUrl()) || urls.add(next.getUrl())) { + retVal.add(next); + } + } + } + return retVal; + } + @Override public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) { for (IValidationSupport next : myChain) { @@ -105,18 +130,4 @@ public class ValidationSupportChain implements IValidationSupport { return myChain.get(0).validateCode(theCtx, theCodeSystem, theCode, theDisplay); } - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - ArrayList retVal = new ArrayList(); - Set urls= new HashSet(); - for (IValidationSupport nextSupport : myChain) { - for (StructureDefinition next : nextSupport.fetchAllStructureDefinitions(theContext)) { - if (isBlank(next.getUrl()) || urls.add(next.getUrl())) { - retVal.add(next); - } - } - } - return retVal; - } - } diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/validation/DefaultProfileValidationSupport.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/validation/DefaultProfileValidationSupport.java index d88b0a2fefd..a674072a212 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/validation/DefaultProfileValidationSupport.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/validation/DefaultProfileValidationSupport.java @@ -1,11 +1,6 @@ package org.hl7.fhir.dstu3.hapi.validation; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.*; - +import ca.uhn.fhir.context.FhirContext; import org.apache.commons.io.Charsets; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; @@ -19,7 +14,11 @@ import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; -import ca.uhn.fhir.context.FhirContext; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.*; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; public class DefaultProfileValidationSupport implements IValidationSupport { @@ -52,6 +51,15 @@ public class DefaultProfileValidationSupport implements IValidationSupport { return retVal; } + @Override + public List fetchAllConformanceResources(FhirContext theContext) { + ArrayList retVal = new ArrayList<>(); + retVal.addAll(myCodeSystems.values()); + retVal.addAll(myStructureDefinitions.values()); + retVal.addAll(myValueSets.values()); + return retVal; + } + @Override public List fetchAllStructureDefinitions(FhirContext theContext) { return new ArrayList(provideStructureDefinitionMap(theContext).values()); @@ -94,7 +102,7 @@ public class DefaultProfileValidationSupport implements IValidationSupport { if (theClass.equals(StructureDefinition.class)) { return (T) fetchStructureDefinition(theContext, theUri); } - + if (theClass.equals(ValueSet.class) || theUri.startsWith(URL_PREFIX_VALUE_SET)) { return (T) fetchValueSet(theContext, theUri); } @@ -195,25 +203,6 @@ public class DefaultProfileValidationSupport implements IValidationSupport { return structureDefinitions; } - @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { - CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem); - if (cs != null) { - boolean caseSensitive = true; - if (cs.hasCaseSensitive()) { - caseSensitive = cs.getCaseSensitive(); - } - - CodeValidationResult retVal = testIfConceptIsInList(theCode, cs.getConcept(), caseSensitive); - - if (retVal != null) { - return retVal; - } - } - - return new CodeValidationResult(IssueSeverity.WARNING, "Unknown code: " + theCodeSystem + " / " + theCode); - } - private CodeValidationResult testIfConceptIsInList(String theCode, List conceptList, boolean theCaseSensitive) { String code = theCode; if (theCaseSensitive == false) { @@ -245,4 +234,23 @@ public class DefaultProfileValidationSupport implements IValidationSupport { return retVal; } + @Override + public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { + CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem); + if (cs != null) { + boolean caseSensitive = true; + if (cs.hasCaseSensitive()) { + caseSensitive = cs.getCaseSensitive(); + } + + CodeValidationResult retVal = testIfConceptIsInList(theCode, cs.getConcept(), caseSensitive); + + if (retVal != null) { + return retVal; + } + } + + return new CodeValidationResult(IssueSeverity.WARNING, "Unknown code: " + theCodeSystem + " / " + theCode); + } + } diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/validation/PrePopulatedValidationSupport.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/validation/PrePopulatedValidationSupport.java index 08820d6c862..1914426daa7 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/validation/PrePopulatedValidationSupport.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/validation/PrePopulatedValidationSupport.java @@ -1,19 +1,21 @@ package org.hl7.fhir.dstu3.hapi.validation; -import static org.apache.commons.lang3.StringUtils.isNotBlank; +import ca.uhn.fhir.context.FhirContext; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.dstu3.model.CodeSystem; +import org.hl7.fhir.dstu3.model.MetadataResource; +import org.hl7.fhir.dstu3.model.StructureDefinition; +import org.hl7.fhir.dstu3.model.ValueSet; +import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; +import org.hl7.fhir.instance.model.api.IBaseResource; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.commons.lang3.Validate; -import org.hl7.fhir.dstu3.model.*; -import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; -import org.hl7.fhir.instance.model.api.IBaseResource; - -import ca.uhn.fhir.context.FhirContext; +import static org.apache.commons.lang3.StringUtils.isNotBlank; /** * This class is an implementation of {@link IValidationSupport} which may be pre-populated @@ -29,23 +31,20 @@ public class PrePopulatedValidationSupport implements IValidationSupport { * Constructor */ public PrePopulatedValidationSupport() { - myStructureDefinitions = new HashMap(); - myValueSets = new HashMap(); - myCodeSystems = new HashMap(); + myStructureDefinitions = new HashMap<>(); + myValueSets = new HashMap<>(); + myCodeSystems = new HashMap<>(); } /** * Constructor - * - * @param theStructureDefinitions - * The StructureDefinitions to be returned by this module. Keys are the logical URL for the resource, and - * values are the resource itself. - * @param theValueSets - * The ValueSets to be returned by this module. Keys are the logical URL for the resource, and values are - * the resource itself. - * @param theCodeSystems - * The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are - * the resource itself. + * + * @param theStructureDefinitions The StructureDefinitions to be returned by this module. Keys are the logical URL for the resource, and + * values are the resource itself. + * @param theValueSets The ValueSets to be returned by this module. Keys are the logical URL for the resource, and values are + * the resource itself. + * @param theCodeSystems The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are + * the resource itself. */ public PrePopulatedValidationSupport(Map theStructureDefinitions, Map theValueSets, Map theCodeSystems) { myStructureDefinitions = theStructureDefinitions; @@ -131,6 +130,15 @@ public class PrePopulatedValidationSupport implements IValidationSupport { return null; } + @Override + public List fetchAllConformanceResources(FhirContext theContext) { + ArrayList retVal = new ArrayList<>(); + retVal.addAll(myCodeSystems.values()); + retVal.addAll(myStructureDefinitions.values()); + retVal.addAll(myValueSets.values()); + return retVal; + } + @Override public List fetchAllStructureDefinitions(FhirContext theContext) { return new ArrayList(myStructureDefinitions.values()); @@ -171,4 +179,4 @@ public class PrePopulatedValidationSupport implements IValidationSupport { return null; } -} \ No newline at end of file +} diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/validation/ValidationSupportChain.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/validation/ValidationSupportChain.java index d7e0d60f469..e859f81746e 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/validation/ValidationSupportChain.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/validation/ValidationSupportChain.java @@ -1,131 +1,143 @@ package org.hl7.fhir.dstu3.hapi.validation; -import static org.apache.commons.lang3.StringUtils.isBlank; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - +import ca.uhn.fhir.context.FhirContext; import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.StructureDefinition; import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.instance.model.api.IBaseResource; -import ca.uhn.fhir.context.FhirContext; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.apache.commons.lang3.StringUtils.isBlank; public class ValidationSupportChain implements IValidationSupport { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidationSupportChain.class); + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidationSupportChain.class); - private List myChain; + private List myChain; - /** - * Constructor - */ - public ValidationSupportChain() { - myChain = new ArrayList(); - } + /** + * Constructor + */ + public ValidationSupportChain() { + myChain = new ArrayList(); + } - /** - * Constructor - */ - public ValidationSupportChain(IValidationSupport... theValidationSupportModules) { - this(); - for (IValidationSupport next : theValidationSupportModules) { - if (next != null) { - myChain.add(next); - } - } - } + /** + * Constructor + */ + public ValidationSupportChain(IValidationSupport... theValidationSupportModules) { + this(); + for (IValidationSupport next : theValidationSupportModules) { + if (next != null) { + myChain.add(next); + } + } + } - public void addValidationSupport(IValidationSupport theValidationSupport) { - myChain.add(theValidationSupport); - } + public void addValidationSupport(IValidationSupport theValidationSupport) { + myChain.add(theValidationSupport); + } - @Override - public ValueSetExpansionComponent expandValueSet(FhirContext theCtx, ConceptSetComponent theInclude) { - for (IValidationSupport next : myChain) { - if (next.isCodeSystemSupported(theCtx, theInclude.getSystem())) { - return next.expandValueSet(theCtx, theInclude); - } - } - return myChain.get(0).expandValueSet(theCtx, theInclude); - } + @Override + public ValueSetExpansionComponent expandValueSet(FhirContext theCtx, ConceptSetComponent theInclude) { + for (IValidationSupport next : myChain) { + if (next.isCodeSystemSupported(theCtx, theInclude.getSystem())) { + return next.expandValueSet(theCtx, theInclude); + } + } + return myChain.get(0).expandValueSet(theCtx, theInclude); + } - @Override - public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) { - for (IValidationSupport next : myChain) { - CodeSystem retVal = next.fetchCodeSystem(theCtx, theSystem); - if (retVal != null) { - return retVal; - } - } - return null; - } + @Override + public List fetchAllConformanceResources(FhirContext theContext) { + List retVal = new ArrayList<>(); + for (IValidationSupport next : myChain) { + List candidates = next.fetchAllConformanceResources(theContext); + if (candidates != null) { + retVal.addAll(candidates); + } + } + return retVal; + } - @Override - public T fetchResource(FhirContext theContext, Class theClass, String theUri) { - for (IValidationSupport next : myChain) { - T retVal = next.fetchResource(theContext, theClass, theUri); - if (retVal != null) { - return retVal; - } - } - return null; - } + @Override + public List fetchAllStructureDefinitions(FhirContext theContext) { + ArrayList retVal = new ArrayList(); + Set urls = new HashSet(); + for (IValidationSupport nextSupport : myChain) { + for (StructureDefinition next : nextSupport.fetchAllStructureDefinitions(theContext)) { + if (isBlank(next.getUrl()) || urls.add(next.getUrl())) { + retVal.add(next); + } + } + } + return retVal; + } - @Override - public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { - for (IValidationSupport next : myChain) { - StructureDefinition retVal = next.fetchStructureDefinition(theCtx, theUrl); - if (retVal != null) { - return retVal; - } - } - return null; - } + @Override + public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) { + for (IValidationSupport next : myChain) { + CodeSystem retVal = next.fetchCodeSystem(theCtx, theSystem); + if (retVal != null) { + return retVal; + } + } + return null; + } - @Override - public boolean isCodeSystemSupported(FhirContext theCtx, String theSystem) { - for (IValidationSupport next : myChain) { - if (next.isCodeSystemSupported(theCtx, theSystem)) { - return true; - } - } - return false; - } + @Override + public T fetchResource(FhirContext theContext, Class theClass, String theUri) { + for (IValidationSupport next : myChain) { + T retVal = next.fetchResource(theContext, theClass, theUri); + if (retVal != null) { + return retVal; + } + } + return null; + } - @Override - public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay) { - - ourLog.info("Validating code {} in chain with {} items", theCode, myChain.size()); - - for (IValidationSupport next : myChain) { - if (next.isCodeSystemSupported(theCtx, theCodeSystem)) { - CodeValidationResult result = next.validateCode(theCtx, theCodeSystem, theCode, theDisplay); - ourLog.info("Chain item {} returned outcome {}", next, result.isOk()); - return result; - } else { - ourLog.info("Chain item {} does not support code system {}", next, theCodeSystem); - } - } - return myChain.get(0).validateCode(theCtx, theCodeSystem, theCode, theDisplay); - } + @Override + public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { + for (IValidationSupport next : myChain) { + StructureDefinition retVal = next.fetchStructureDefinition(theCtx, theUrl); + if (retVal != null) { + return retVal; + } + } + return null; + } + + @Override + public boolean isCodeSystemSupported(FhirContext theCtx, String theSystem) { + for (IValidationSupport next : myChain) { + if (next.isCodeSystemSupported(theCtx, theSystem)) { + return true; + } + } + return false; + } + + @Override + public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay) { + + ourLog.info("Validating code {} in chain with {} items", theCode, myChain.size()); + + for (IValidationSupport next : myChain) { + if (next.isCodeSystemSupported(theCtx, theCodeSystem)) { + CodeValidationResult result = next.validateCode(theCtx, theCodeSystem, theCode, theDisplay); + ourLog.info("Chain item {} returned outcome {}", next, result.isOk()); + return result; + } else { + ourLog.info("Chain item {} does not support code system {}", next, theCodeSystem); + } + } + return myChain.get(0).validateCode(theCtx, theCodeSystem, theCode, theDisplay); + } - @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - ArrayList retVal = new ArrayList(); - Set urls= new HashSet(); - for (IValidationSupport nextSupport : myChain) { - for (StructureDefinition next : nextSupport.fetchAllStructureDefinitions(theContext)) { - if (isBlank(next.getUrl()) || urls.add(next.getUrl())) { - retVal.add(next); - } - } - } - return retVal; - } } diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/DefaultProfileValidationSupport.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/DefaultProfileValidationSupport.java index 1961fa84b46..120e25e8567 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/DefaultProfileValidationSupport.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/DefaultProfileValidationSupport.java @@ -1,11 +1,6 @@ package org.hl7.fhir.r4.hapi.ctx; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.*; - +import ca.uhn.fhir.context.FhirContext; import org.apache.commons.io.Charsets; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; @@ -14,10 +9,16 @@ import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode; import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; -import org.hl7.fhir.r4.model.ValueSet.*; +import org.hl7.fhir.r4.model.ValueSet.ConceptReferenceComponent; +import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; -import ca.uhn.fhir.context.FhirContext; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.*; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; public class DefaultProfileValidationSupport implements IValidationSupport { @@ -51,10 +52,20 @@ public class DefaultProfileValidationSupport implements IValidationSupport { } @Override - public List fetchAllStructureDefinitions(FhirContext theContext) { - return new ArrayList(provideStructureDefinitionMap(theContext).values()); + public List fetchAllConformanceResources(FhirContext theContext) { + ArrayList retVal = new ArrayList<>(); + retVal.addAll(myCodeSystems.values()); + retVal.addAll(myStructureDefinitions.values()); + retVal.addAll(myValueSets.values()); + return retVal; } + @Override + public List fetchAllStructureDefinitions(FhirContext theContext) { + return new ArrayList<>(provideStructureDefinitionMap(theContext).values()); + } + + @Override public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { return (CodeSystem) fetchCodeSystemOrValueSet(theContext, theSystem, true); @@ -65,8 +76,8 @@ public class DefaultProfileValidationSupport implements IValidationSupport { Map codeSystems = myCodeSystems; Map valueSets = myValueSets; if (codeSystems == null || valueSets == null) { - codeSystems = new HashMap(); - valueSets = new HashMap(); + codeSystems = new HashMap<>(); + valueSets = new HashMap<>(); loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/r4/model/valueset/valuesets.xml"); loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/r4/model/valueset/v2-tables.xml"); @@ -92,7 +103,7 @@ public class DefaultProfileValidationSupport implements IValidationSupport { if (theClass.equals(StructureDefinition.class)) { return (T) fetchStructureDefinition(theContext, theUri); } - + if (theClass.equals(ValueSet.class) || theUri.startsWith(URL_PREFIX_VALUE_SET)) { return (T) fetchValueSet(theContext, theUri); } @@ -193,25 +204,6 @@ public class DefaultProfileValidationSupport implements IValidationSupport { return structureDefinitions; } - @Override - public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { - CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem); - if (cs != null) { - boolean caseSensitive = true; - if (cs.hasCaseSensitive()) { - caseSensitive = cs.getCaseSensitive(); - } - - CodeValidationResult retVal = testIfConceptIsInList(theCode, cs.getConcept(), caseSensitive); - - if (retVal != null) { - return retVal; - } - } - - return new CodeValidationResult(IssueSeverity.WARNING, "Unknown code: " + theCodeSystem + " / " + theCode); - } - private CodeValidationResult testIfConceptIsInList(String theCode, List conceptList, boolean theCaseSensitive) { String code = theCode; if (theCaseSensitive == false) { @@ -243,4 +235,23 @@ public class DefaultProfileValidationSupport implements IValidationSupport { return retVal; } + @Override + public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { + CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem); + if (cs != null) { + boolean caseSensitive = true; + if (cs.hasCaseSensitive()) { + caseSensitive = cs.getCaseSensitive(); + } + + CodeValidationResult retVal = testIfConceptIsInList(theCode, cs.getConcept(), caseSensitive); + + if (retVal != null) { + return retVal; + } + } + + return new CodeValidationResult(IssueSeverity.WARNING, "Unknown code: " + theCodeSystem + " / " + theCode); + } + } diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/PrePopulatedValidationSupport.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/PrePopulatedValidationSupport.java index 7545997ff31..92807c4f6c3 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/PrePopulatedValidationSupport.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/PrePopulatedValidationSupport.java @@ -1,16 +1,21 @@ package org.hl7.fhir.r4.hapi.ctx; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -import java.util.*; - +import ca.uhn.fhir.context.FhirContext; import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.MetadataResource; +import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent; -import ca.uhn.fhir.context.FhirContext; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; /** * This class is an implementation of {@link IValidationSupport} which may be pre-populated @@ -26,23 +31,21 @@ public class PrePopulatedValidationSupport implements IValidationSupport { * Constructor */ public PrePopulatedValidationSupport() { - myStructureDefinitions = new HashMap(); - myValueSets = new HashMap(); - myCodeSystems = new HashMap(); + myStructureDefinitions = new HashMap<>(); + myValueSets = new HashMap<>(); + myCodeSystems = new HashMap<>(); } + /** * Constructor - * - * @param theStructureDefinitions - * The StructureDefinitions to be returned by this module. Keys are the logical URL for the resource, and - * values are the resource itself. - * @param theValueSets - * The ValueSets to be returned by this module. Keys are the logical URL for the resource, and values are - * the resource itself. - * @param theCodeSystems - * The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are - * the resource itself. + * + * @param theStructureDefinitions The StructureDefinitions to be returned by this module. Keys are the logical URL for the resource, and + * values are the resource itself. + * @param theValueSets The ValueSets to be returned by this module. Keys are the logical URL for the resource, and values are + * the resource itself. + * @param theCodeSystems The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are + * the resource itself. */ public PrePopulatedValidationSupport(Map theStructureDefinitions, Map theValueSets, Map theCodeSystems) { myStructureDefinitions = theStructureDefinitions; @@ -128,6 +131,15 @@ public class PrePopulatedValidationSupport implements IValidationSupport { return null; } + @Override + public List fetchAllConformanceResources(FhirContext theContext) { + ArrayList retVal = new ArrayList<>(); + retVal.addAll(myCodeSystems.values()); + retVal.addAll(myStructureDefinitions.values()); + retVal.addAll(myValueSets.values()); + return retVal; + } + @Override public List fetchAllStructureDefinitions(FhirContext theContext) { return new ArrayList(myStructureDefinitions.values()); @@ -168,4 +180,4 @@ public class PrePopulatedValidationSupport implements IValidationSupport { return null; } -} \ No newline at end of file +} diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/ValidationSupportChain.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/ValidationSupportChain.java index 64d29bf119f..0ab5873784d 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/ValidationSupportChain.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/ValidationSupportChain.java @@ -1,16 +1,18 @@ package org.hl7.fhir.r4.hapi.ctx; -import static org.apache.commons.lang3.StringUtils.isBlank; - -import java.util.*; - +import ca.uhn.fhir.context.FhirContext; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.StructureDefinition; import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent; -import ca.uhn.fhir.context.FhirContext; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.apache.commons.lang3.StringUtils.isBlank; public class ValidationSupportChain implements IValidationSupport { @@ -51,7 +53,19 @@ public class ValidationSupportChain implements IValidationSupport { return myChain.get(0).expandValueSet(theCtx, theInclude); } - @Override + @Override + public List fetchAllConformanceResources(FhirContext theContext) { + List retVal = new ArrayList<>(); + for (IValidationSupport next : myChain) { + List candidates = next.fetchAllConformanceResources(theContext); + if (candidates != null) { + retVal.addAll(candidates); + } + } + return retVal; + } + + @Override public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) { for (IValidationSupport next : myChain) { CodeSystem retVal = next.fetchCodeSystem(theCtx, theSystem); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/LoggingInterceptorTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/LoggingInterceptorTest.java index d6bfee274a6..6e7f3c6d413 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/LoggingInterceptorTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/LoggingInterceptorTest.java @@ -85,7 +85,7 @@ public class LoggingInterceptorTest { System.out.flush(); return formattedMessage.contains("Client request: GET http://localhost:" + ourPort + "/Patient/1 HTTP/1.1") || - formattedMessage.contains("Client response: HTTP 200 OK (Location: http://localhost:" + ourPort + "/Patient/1/_history/1)"); + formattedMessage.contains("Client response: HTTP 200 OK (Patient/1/_history/1)"); } })); } diff --git a/pom.xml b/pom.xml index 8eedd207397..a859dfaafab 100644 --- a/pom.xml +++ b/pom.xml @@ -1828,6 +1828,7 @@ example-projects/hapi-fhir-base-example-embedded-ws example-projects/hapi-fhir-standalone-overlay-example hapi-fhir-jacoco + hapi-fhir-igpacks @@ -1875,6 +1876,4 @@ - -