Work on CLI tool

This commit is contained in:
jamesagnew 2015-09-13 22:06:31 -04:00
parent 780fc871cb
commit d0bac3d419
30 changed files with 1626 additions and 305 deletions

View File

@ -9,6 +9,8 @@ import javax.servlet.ServletException;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.WildcardFileFilter; import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.hl7.fhir.instance.model.ValueSet; import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
@ -21,6 +23,8 @@ import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.client.IGenericClient; import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.validation.DefaultProfileValidationSupport;
import ca.uhn.fhir.validation.ValidationSupportChain;
import ca.uhn.fhir.validation.FhirInstanceValidator; import ca.uhn.fhir.validation.FhirInstanceValidator;
import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.IValidationSupport; import ca.uhn.fhir.validation.IValidationSupport;
@ -172,7 +176,7 @@ public class ValidatorExamples {
IValidationSupport valSupport = new IValidationSupport() { IValidationSupport valSupport = new IValidationSupport() {
@Override @Override
public org.hl7.fhir.instance.utils.IWorkerContext.ValidationResult validateCode(String theCodeSystem, String theCode, String theDisplay) { public CodeValidationResult validateCode(String theCodeSystem, String theCode, String theDisplay) {
// TODO: Implement // TODO: Implement
return null; return null;
} }
@ -194,8 +198,23 @@ public class ValidatorExamples {
// TODO: Implement // TODO: Implement
return null; return null;
} }
@Override
public ValueSetExpansionComponent expandValueSet(ConceptSetComponent theInclude) {
// TODO: Implement
return null;
}
}; };
instanceValidator.setValidationSupport(valSupport);
/*
* ValidationSupportChain strings multiple instances of IValidationSupport together. The
* code below is useful because it means that when the validator wants to load a
* StructureDefinition or a ValueSet, it will first use DefaultProfileValidationSupport,
* which loads the default HL7 versions. Any StructureDefinitions which are not found in
* the built-in set are delegated to your custom implementation.
*/
ValidationSupportChain support = new ValidationSupportChain(new DefaultProfileValidationSupport(), valSupport);
instanceValidator.setValidationSupport(support);
// END SNIPPET: instanceValidatorCustom // END SNIPPET: instanceValidatorCustom
} }

View File

@ -1101,7 +1101,9 @@ public class JsonParser extends BaseParser implements IParser {
continue; continue;
} }
// _id is incorrect, but some early examples in the FHIR spec used it // _id is incorrect, but some early examples in the FHIR spec used it
elementId = theObject.getString(nextName); if (theObject.get(nextName).getValueType() == ValueType.STRING) {
elementId = theObject.getString(nextName);
}
continue; continue;
} else if ("extension".equals(nextName)) { } else if ("extension".equals(nextName)) {
JsonArray array = theObject.getJsonArray(nextName); JsonArray array = theObject.getJsonArray(nextName);

View File

@ -1076,6 +1076,8 @@ class ParserState<T> {
} else if ("fullUrl".equals(theLocalPart)) { } else if ("fullUrl".equals(theLocalPart)) {
myFullUrl = new IdDt(); myFullUrl = new IdDt();
push(new PrimitiveState(getPreResourceState(), myFullUrl)); push(new PrimitiveState(getPreResourceState(), myFullUrl));
} else if ("fhir_comments".equals(theLocalPart) && myJsonMode) {
push(new SwallowChildrenWholeState(getPreResourceState()));
} else { } else {
throw new DataFormatException("Unexpected element in entry: " + theLocalPart); throw new DataFormatException("Unexpected element in entry: " + theLocalPart);
} }

View File

@ -1,8 +1,8 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<!-- Note: HAPI projects use the "hapi-fhir" POM as their base to provide easy management. You do not need to use this in your own projects, so the "parent" tag and it's contents below may be removed <!-- Note: HAPI projects use the "hapi-fhir" POM as their base to provide easy management. You do not need to use this in your own projects, so the "parent" tag
if you are using this file as a basis for your own project. --> and it's contents below may be removed if you are using this file as a basis for your own project. -->
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
@ -30,6 +30,11 @@
<version>1.2-SNAPSHOT</version> <version>1.2-SNAPSHOT</version>
<type>war</type> <type>war</type>
</dependency> </dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu</artifactId>
<version>1.2-SNAPSHOT</version>
</dependency>
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu2</artifactId> <artifactId>hapi-fhir-structures-dstu2</artifactId>
@ -91,7 +96,34 @@
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId> <artifactId>jetty-webapp</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.ebaysf.web</groupId>
<artifactId>cors-filter</artifactId>
</dependency>
<dependency>
<groupId>com.phloc</groupId>
<artifactId>phloc-schematron</artifactId>
</dependency>
<dependency>
<groupId>com.phloc</groupId>
<artifactId>phloc-commons</artifactId>
</dependency>
<dependency>
<groupId>org.fusesource.jansi</groupId>
<artifactId>jansi</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>
@ -163,6 +195,35 @@
</executions> </executions>
</plugin> </plugin>
</plugins> </plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself. -->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<versionRange>[2.10,)</versionRange>
<goals>
<goal>copy</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build> </build>
</project> </project>

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.cli; package ca.uhn.fhir.cli;
import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -7,26 +8,130 @@ import java.util.List;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options; import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException; import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.WordUtils;
import org.fusesource.jansi.AnsiConsole;
import org.slf4j.LoggerFactory;
import ca.uhn.fhir.util.VersionUtil;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import static org.fusesource.jansi.Ansi.*;
import static org.fusesource.jansi.Ansi.Color.*;
import static org.fusesource.jansi.Ansi.*;
import static org.fusesource.jansi.Ansi.Color.*;
public class App { public class App {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(App.class);
private static List<BaseCommand> ourCommands; private static List<BaseCommand> ourCommands;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(App.class);
static { static {
ourCommands = new ArrayList<BaseCommand>(); ourCommands = new ArrayList<BaseCommand>();
ourCommands.add(new RunServerCommand()); ourCommands.add(new RunServerCommand());
ourCommands.add(new ExampleDataUploader());
Collections.sort(ourCommands); Collections.sort(ourCommands);
} }
private static void logCommandUsage(BaseCommand theCommand) {
logAppHeader();
System.out.println("Usage:");
System.out.println(" hapi-fhir-cli " + theCommand.getCommandName() + " [options]");
System.out.println();
System.out.println("Options:");
HelpFormatter fmt = new HelpFormatter();
PrintWriter pw = new PrintWriter(System.out);
fmt.printOptions(pw, 80, theCommand.getOptions(), 2, 2);
pw.flush();
pw.close();
}
private static void loggingConfigOff() {
try {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext((LoggerContext) LoggerFactory.getILoggerFactory());
((LoggerContext) LoggerFactory.getILoggerFactory()).reset();
configurator.doConfigure(App.class.getResourceAsStream("/logback-cli-off.xml"));
} catch (JoranException e) {
e.printStackTrace();
}
}
private static void loggingConfigOn() {
try {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext((LoggerContext) LoggerFactory.getILoggerFactory());
((LoggerContext) LoggerFactory.getILoggerFactory()).reset();
configurator.doConfigure(App.class.getResourceAsStream("/logback-cli-on.xml"));
} catch (JoranException e) {
e.printStackTrace();
}
}
private static void logUsage() {
logAppHeader();
System.out.println("Usage:");
System.out.println(" hapi-fhir-cli {command} [options]");
System.out.println();
System.out.println("Commands:");
for (BaseCommand next : ourCommands) {
String left = " " + next.getCommandName() + " - ";
String[] rightParts = WordUtils.wrap(next.getCommandDescription(), 80 - left.length()).split("\\n");
for (int i = 1; i < rightParts.length; i++) {
rightParts[i] = StringUtils.leftPad("", left.length()) + rightParts[i];
}
System.out.println(left + StringUtils.join(rightParts, '\n'));
}
System.out.println();
System.out.println("See what options are available:");
System.out.println(" hapi-fhir-cli help {command}");
System.out.println();
}
private static void logAppHeader() {
System.out.flush();
System.out.println("------------------------------------------------------------");
System.out.println("\ud83d\udd25 " + ansi().bold() + "HAPI FHIR" + ansi().boldOff() + " " + VersionUtil.getVersion() + " - Command Line Tool");
System.out.println("------------------------------------------------------------");
}
public static void main(String[] theArgs) { public static void main(String[] theArgs) {
loggingConfigOff();
AnsiConsole.systemInstall();
// log version while the logging is off
VersionUtil.getVersion();
if (theArgs.length == 0) { if (theArgs.length == 0) {
logUsage(); logUsage();
return; return;
} }
if (theArgs[0].equals("help")) {
if (theArgs.length < 2) {
logUsage();
return;
}
BaseCommand command = null;
for (BaseCommand nextCommand : ourCommands) {
if (nextCommand.getCommandName().equals(theArgs[1])) {
command = nextCommand;
break;
}
}
if (command == null) {
System.err.println("Unknown command: " + theArgs[1]);
return;
}
logCommandUsage(command);
return;
}
BaseCommand command = null; BaseCommand command = null;
for (BaseCommand nextCommand : ourCommands) { for (BaseCommand nextCommand : ourCommands) {
if (nextCommand.getCommandName().equals(theArgs[0])) { if (nextCommand.getCommandName().equals(theArgs[0])) {
@ -41,18 +146,24 @@ public class App {
try { try {
String[] args = Arrays.asList(theArgs).subList(1, theArgs.length).toArray(new String[theArgs.length - 1]); String[] args = Arrays.asList(theArgs).subList(1, theArgs.length).toArray(new String[theArgs.length - 1]);
parsedOptions = parser.parse(options, args, true); parsedOptions = parser.parse(options, args, true);
logAppHeader();
loggingConfigOn();
// Actually execute the command
command.run(parsedOptions); command.run(parsedOptions);
} catch (ParseException e) { } catch (ParseException e) {
ourLog.error("Invalid command options for command: " + command.getCommandName()); ourLog.error("Invalid command options for command: " + command.getCommandName());
ourLog.error(e.getMessage()); ourLog.error(e.getMessage());
ourLog.error("Aborting!"); ourLog.error("Aborting!");
return; return;
} catch (CommandFailureException e) {
ourLog.error(e.getMessage());
} catch (Exception e) {
ourLog.error("Error during execution: ", e);
} }
} }
private static void logUsage() {
}
} }

View File

@ -13,20 +13,23 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
super(); super();
} }
protected IGenericClient newClient(FhirContext ctx) {
IGenericClient fhirClient = ctx.newRestfulGenericClient("http://fhirtest.uhn.ca/baseDstu2");
return fhirClient;
}
public abstract Options getOptions();
public abstract String getCommandName();
public abstract void run(CommandLine theCommandLine) throws ParseException;
@Override @Override
public int compareTo(BaseCommand theO) { public int compareTo(BaseCommand theO) {
return getCommandName().compareTo(theO.getCommandName()); return getCommandName().compareTo(theO.getCommandName());
} }
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;
} }

View File

@ -0,0 +1,11 @@
package ca.uhn.fhir.cli;
public class CommandFailureException extends Exception {
public CommandFailureException(String theMessage) {
super(theMessage);
}
private static final long serialVersionUID = 1L;
}

View File

@ -1,17 +1,22 @@
package ca.uhn.fhir.cli; package ca.uhn.fhir.cli;
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.util.HashMap;
import java.io.UnsupportedEncodingException;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
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.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
@ -23,32 +28,72 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
import ca.uhn.fhir.model.dstu2.resource.Bundle.EntryRequest; import ca.uhn.fhir.model.dstu2.resource.Bundle.EntryRequest;
import ca.uhn.fhir.model.dstu2.resource.DataElement;
import ca.uhn.fhir.model.dstu2.resource.SearchParameter; import ca.uhn.fhir.model.dstu2.resource.SearchParameter;
import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum; import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.client.IGenericClient; import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.util.ResourceReferenceInfo; import ca.uhn.fhir.util.ResourceReferenceInfo;
public class ExampleDataUploader extends BaseCommand { public class ExampleDataUploader extends BaseCommand {
private static final String SPEC_DEFAULT_VERSION = "2015Sep";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleDataUploader.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleDataUploader.class);
public static void main(String[] args) throws Exception { @Override
new ExampleDataUploader().execute(); public String getCommandDescription() {
return "Downloads the resource example pack from the HL7.org FHIR specification website, and uploads all of the example resources to a given server.";
} }
private void execute() throws IOException, ClientProtocolException, UnsupportedEncodingException { @Override
ourLog.info("Starting..."); public String getCommandName() {
return "upload-examples";
}
@Override
public Options getOptions() {
Options options = new Options();
Option opt;
opt = new Option("f", "fhirversion", true, "Spec version to upload (default is '" + SPEC_DEFAULT_VERSION + "')");
opt.setRequired(false);
options.addOption(opt);
opt = new Option("t", "target", true, "Base URL for the target server");
opt.setRequired(true);
options.addOption(opt);
return options;
}
@Override
public void run(CommandLine theCommandLine) throws Exception {
String specVersion = theCommandLine.getOptionValue("f", SPEC_DEFAULT_VERSION);
String targetServer = theCommandLine.getOptionValue("t");
if (isBlank(targetServer)) {
throw new ParseException("No target server (-t) specified");
} else if (targetServer.startsWith("http") == false) {
throw new ParseException("Invalid target server specified, must begin with 'http'");
}
FhirContext ctx = FhirContext.forDstu2(); FhirContext ctx = FhirContext.forDstu2();
String specUrl = "http://hl7.org/fhir/" + specVersion + "/examples-json.zip";
HttpGet get = new HttpGet("http://hl7.org/fhir/2015May/examples-json.zip"); ourLog.info("HTTP fetching: {}", specUrl);
HttpGet get = new HttpGet(specUrl);
CloseableHttpClient client = HttpClientBuilder.create().build(); CloseableHttpClient client = HttpClientBuilder.create().build();
CloseableHttpResponse result = client.execute(get); CloseableHttpResponse result = client.execute(get);
if (result.getStatusLine().getStatusCode() != 200) {
throw new CommandFailureException("Got HTTP " + result.getStatusLine().getStatusCode() + " response code loading " + specUrl);
}
byte[] bytes = IOUtils.toByteArray(result.getEntity().getContent()); byte[] bytes = IOUtils.toByteArray(result.getEntity().getContent());
IOUtils.closeQuietly(result.getEntity().getContent()); IOUtils.closeQuietly(result.getEntity().getContent());
ourLog.info("Loaded examples ({} bytes)", bytes.length); ourLog.info("Successfully Loaded example pack ({} bytes)", bytes.length);
ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(bytes)); ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(bytes));
byte[] buffer = new byte[2048]; byte[] buffer = new byte[2048];
@ -69,6 +114,10 @@ public class ExampleDataUploader extends BaseCommand {
byte[] exampleBytes = bos.toByteArray(); byte[] exampleBytes = bos.toByteArray();
String exampleString = new String(exampleBytes, "UTF-8"); String exampleString = new String(exampleBytes, "UTF-8");
if (ourLog.isTraceEnabled()) {
ourLog.trace("Next example: " + exampleString);
}
IBaseResource parsed; IBaseResource parsed;
try { try {
parsed = ctx.newJsonParser().parseResource(exampleString); parsed = ctx.newJsonParser().parseResource(exampleString);
@ -101,16 +150,60 @@ public class ExampleDataUploader extends BaseCommand {
} }
Set<String> ids = new HashSet<String>(); Map<String, Integer> ids = new HashMap<String, Integer>();
Set<String> fullIds = new HashSet<String>();
for (int i = 0; i < bundle.getEntry().size(); i++) {
Entry next = bundle.getEntry().get(i);
// DataElement have giant IDs that seem invalid, need to investigate this..
if ("DataElement".equals(next.getResource().getResourceName()) || "OperationOutcome".equals(next.getResource().getResourceName())
|| "OperationDefinition".equals(next.getResource().getResourceName())) {
ourLog.info("Skipping " + next.getResource().getResourceName() + " example");
bundle.getEntry().remove(i);
i--;
} else {
IdDt resourceId = next.getResource().getId();
if (!fullIds.add(resourceId.toUnqualifiedVersionless().getValue())) {
ourLog.info("Discarding duplicate resource: " + resourceId.getValue());
bundle.getEntry().remove(i);
i--;
continue;
}
String idPart = resourceId.getIdPart();
if (idPart != null) {
if (!ids.containsKey(idPart)) {
ids.put(idPart, 1);
} else {
ids.put(idPart, ids.get(idPart) + 1);
}
} else {
ourLog.info("Discarding resource with not explicit ID");
bundle.getEntry().remove(i);
i--;
}
}
}
Set<String> qualIds = new HashSet<String>();
Map<String, String> renames = new HashMap<String,String>();
for (int i = 0; i < bundle.getEntry().size(); i++) { for (int i = 0; i < bundle.getEntry().size(); i++) {
Entry next = bundle.getEntry().get(i); Entry next = bundle.getEntry().get(i);
if (next.getResource().getId().getIdPart() != null) { if (next.getResource().getId().getIdPart() != null) {
String nextId = next.getResource().getResourceName() + '/' + next.getResource().getId().getIdPart(); String idPart = next.getResource().getId().getIdPart();
if (!ids.add(nextId)) { String originalId = next.getResource().getResourceName() + '/' + idPart;
if (ids.get(idPart) > 1 || next.getResource().getId().isIdPartValidLong()) {
idPart = next.getResource().getResourceName() + idPart;
}
String nextId = next.getResource().getResourceName() + '/' + idPart;
if (!qualIds.add(nextId)) {
ourLog.info("Discarding duplicate resource with ID: " + nextId); ourLog.info("Discarding duplicate resource with ID: " + nextId);
bundle.getEntry().remove(i); bundle.getEntry().remove(i);
i--; i--;
} }
next.getRequest().setMethod(HTTPVerbEnum.PUT);
next.getRequest().setUrl(nextId);
renames.put(originalId, nextId);
} }
} }
@ -119,14 +212,20 @@ public class ExampleDataUploader extends BaseCommand {
List<ResourceReferenceInfo> refs = ctx.newTerser().getAllResourceReferences(next.getResource()); List<ResourceReferenceInfo> refs = ctx.newTerser().getAllResourceReferences(next.getResource());
for (ResourceReferenceInfo nextRef : refs) { for (ResourceReferenceInfo nextRef : refs) {
// if (nextRef.getResourceReference().getReferenceElement().isAbsolute()) { // if (nextRef.getResourceReference().getReferenceElement().isAbsolute()) {
// ourLog.info("Discarding absolute reference: {}", nextRef.getResourceReference().getReferenceElement().getValue()); // ourLog.info("Discarding absolute reference: {}",
// nextRef.getResourceReference().getReferenceElement().getValue());
// nextRef.getResourceReference().getReferenceElement().setValue(null); // nextRef.getResourceReference().getReferenceElement().setValue(null);
// } // }
nextRef.getResourceReference().getReferenceElement().setValue(nextRef.getResourceReference().getReferenceElement().toUnqualifiedVersionless().getValue()); nextRef.getResourceReference().getReferenceElement().setValue(nextRef.getResourceReference().getReferenceElement().toUnqualifiedVersionless().getValue());
String value = nextRef.getResourceReference().getReferenceElement().toUnqualifiedVersionless().getValue(); String value = nextRef.getResourceReference().getReferenceElement().toUnqualifiedVersionless().getValue();
if (!ids.contains(value) && !nextRef.getResourceReference().getReferenceElement().isLocal()) { if (!qualIds.contains(value) && !nextRef.getResourceReference().getReferenceElement().isLocal()) {
if (renames.containsKey(value)) {
nextRef.getResourceReference().setReference(renames.get(value));
goodRefs++;
} else {
ourLog.info("Discarding unknown reference: {}", value); ourLog.info("Discarding unknown reference: {}", value);
nextRef.getResourceReference().getReferenceElement().setValue(null); nextRef.getResourceReference().getReferenceElement().setValue(null);
}
} else { } else {
goodRefs++; goodRefs++;
} }
@ -134,8 +233,10 @@ public class ExampleDataUploader extends BaseCommand {
} }
// for (Entry next : bundle.getEntry()) { // for (Entry next : bundle.getEntry()) {
// if (next.getResource().getId().hasIdPart() && Character.isLetter(next.getResource().getId().getIdPart().charAt(0))) { // if (next.getResource().getId().hasIdPart() &&
// next.getTransaction().setUrl(next.getResource().getResourceName() + '/' + next.getResource().getId().getIdPart()); // Character.isLetter(next.getResource().getId().getIdPart().charAt(0))) {
// next.getTransaction().setUrl(next.getResource().getResourceName() + '/' +
// next.getResource().getId().getIdPart());
// next.getTransaction().setMethod(HTTPVerbEnum.PUT); // next.getTransaction().setMethod(HTTPVerbEnum.PUT);
// } // }
// } // }
@ -146,8 +247,16 @@ public class ExampleDataUploader extends BaseCommand {
ourLog.info("Final bundle: {} entries", bundle.getEntry().size()); ourLog.info("Final bundle: {} entries", bundle.getEntry().size());
ourLog.info("Final bundle: {} chars", encoded.length()); ourLog.info("Final bundle: {} chars", encoded.length());
IGenericClient fhirClient = newClient(ctx); ourLog.info("Uploading bundle to server: " + targetServer);
IGenericClient fhirClient = newClient(ctx, targetServer);
long start = System.currentTimeMillis();
;
fhirClient.transaction().withBundle(bundle).execute(); fhirClient.transaction().withBundle(bundle).execute();
long delay = System.currentTimeMillis() - start;
ourLog.info("Finished uploading bundle to server (took {} ms)", delay);
} }
} }

View File

@ -66,6 +66,7 @@ public class RunServerCommand extends BaseCommand {
WebAppContext root = new WebAppContext(); WebAppContext root = new WebAppContext();
root.setAllowDuplicateFragmentNames(true); root.setAllowDuplicateFragmentNames(true);
root.setWar(tempWarFile.getAbsolutePath()); root.setWar(tempWarFile.getAbsolutePath());
root.setParentLoaderPriority(true);
root.setContextPath("/"); root.setContextPath("/");
myServer = new Server(myPort); myServer = new Server(myPort);
@ -99,4 +100,9 @@ public class RunServerCommand extends BaseCommand {
} }
@Override
public String getCommandDescription() {
return "Start a FHIR server which can be used for testing";
}
} }

View File

@ -3,6 +3,9 @@ package ca.uhn.fhir.cli;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.client.ClientProtocolException; import org.apache.http.client.ClientProtocolException;
import org.hl7.fhir.instance.model.Bundle; import org.hl7.fhir.instance.model.Bundle;
@ -29,7 +32,7 @@ public class ValidationDataUploader extends BaseCommand {
FhirContext ctx = FhirContext.forDstu2Hl7Org(); FhirContext ctx = FhirContext.forDstu2Hl7Org();
IGenericClient client = newClient(ctx); IGenericClient client = newClient(ctx,"");
int total; int total;
int count; int count;
@ -104,4 +107,28 @@ public class ValidationDataUploader extends BaseCommand {
} }
@Override
public String getCommandDescription() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getCommandName() {
// TODO Auto-generated method stub
return null;
}
@Override
public Options getOptions() {
// TODO Auto-generated method stub
return null;
}
@Override
public void run(CommandLine theCommandLine) throws ParseException {
// TODO Auto-generated method stub
}
} }

View File

@ -0,0 +1,14 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss} %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<root level="error">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@ -0,0 +1,46 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<useJansi>true</useJansi>
<encoder>
<pattern>%boldGreen(%d{HH:mm:ss}) %white(%-5level) %logger{36} - %boldWhite(%msg%n)
</pattern>
</encoder>
</appender>
<logger name="java.sql" additivity="false" level="warn">
<appender-ref ref="STDOUT" />
</logger>
<logger name="ca.uhn.fhir.jpa" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="ca.uhn.fhir.rest" additivity="false" level="warn">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.eclipse" additivity="false" level="warn">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.springframework" additivity="false" level="warn">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.apache" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.thymeleaf" additivity="false" level="warn">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.hibernate" additivity="false" level="warn">
<appender-ref ref="STDOUT" />
</logger>
<!--
<logger name="ca.uhn.fhir.rest.client" additivity="false" level="trace">
<appender-ref ref="STDOUT" />
</logger>
-->
<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@ -1,30 +0,0 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] - %msg%n
</pattern>
</encoder>
</appender>
<logger name="org.eclipse" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.apache" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.thymeleaf" additivity="false" level="warn">
<appender-ref ref="STDOUT" />
</logger>
<!--
<logger name="ca.uhn.fhir.rest.client" additivity="false" level="trace">
<appender-ref ref="STDOUT" />
</logger>
-->
<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@ -0,0 +1,132 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# HAPI-FHIR-CLI Startup Script
#
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
#
# Look for the Apple JDKs first to preserve the existing behaviour, and then look
# for the new JDKs provided by Oracle.
#
if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then
#
# Apple JDKs
#
export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
fi
if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then
#
# Apple JDKs
#
export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
fi
if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then
#
# Oracle JDKs
#
export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
fi
if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then
#
# Apple JDKs
#
export JAVA_HOME=`/usr/libexec/java_home`
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# For Migwn, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
SCRIPTDIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
exec "$JAVACMD" \
$CLI_OPTS \
-jar $SCRIPTDIR/hapi-fhir-cli.jar "$@"

View File

@ -0,0 +1,135 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM HAPI-FHIR CLI Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM ----------------------------------------------------------------------------
@echo off
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto chkMHome
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:chkMHome
if not "%CLI_HOME%"=="" goto valMHome
SET "CLI_HOME=%~dp0."
if not "%CLI_HOME%"=="" goto valMHome
echo.
echo Error: CLI_HOME not found in your environment. >&2
echo Please set the CLI_HOME variable in your environment to match the >&2
echo location of the Maven installation. >&2
echo.
goto error
:valMHome
:stripMHome
if not "_%CLI_HOME:~-1%"=="_\" goto checkMCmd
set "CLI_HOME=%CLI_HOME:~0,-1%"
goto stripMHome
:checkMCmd
if exist "%CLI_HOME%\hapi-fhir-cli.jar" goto init
echo.
echo Error: CLI_HOME is set to an invalid directory. >&2
echo CLI_HOME = "%CLI_HOME%" >&2
echo Please set the CLI_HOME variable in your environment to match the >&2
echo location of the Maven installation >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
set CLI_CMD_LINE_ARGS=%*
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
SET CLI_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
%CLI_JAVA_EXE% %JVM_CONFIG_CLI_PROPS% -jar %CLI_HOME%\hapi-fhir-cli.jar %CLI_CMD_LINE_ARGS%
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
exit /B %ERROR_CODE%

View File

@ -275,6 +275,11 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
// TODO: process verbs in the correct order // TODO: process verbs in the correct order
for (int i = 0; i < theRequest.getEntry().size(); i++) { for (int i = 0; i < theRequest.getEntry().size(); i++) {
if (i % 100 == 0) {
ourLog.info("Processed {} entries out of {}", i, theRequest.getEntry().size());
}
Entry nextEntry = theRequest.getEntry().get(i); Entry nextEntry = theRequest.getEntry().get(i);
IResource res = nextEntry.getResource(); IResource res = nextEntry.getResource();
IdDt nextResourceId = null; IdDt nextResourceId = null;
@ -313,7 +318,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
case POST: { case POST: {
// CREATE // CREATE
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
IFhirResourceDao resourceDao = getDao(res.getClass()); IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass());
res.setId((String) null); res.setId((String) null);
DaoMethodOutcome outcome; DaoMethodOutcome outcome;
Entry newEntry = response.addEntry(); Entry newEntry = response.addEntry();
@ -338,7 +343,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
case PUT: { case PUT: {
// UPDATE // UPDATE
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
IFhirResourceDao resourceDao = getDao(res.getClass()); IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass());
DaoMethodOutcome outcome; DaoMethodOutcome outcome;
Entry newEntry = response.addEntry(); Entry newEntry = response.addEntry();
@ -490,6 +495,14 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
return response; return response;
} }
private IFhirResourceDao<?> getDaoOrThrowException(Class<? extends IResource> theClass) {
IFhirResourceDao<? extends IResource> retVal = getDao(theClass);
if (retVal == null) {
throw new InvalidRequestException("Unable to process request, this server does not know how to handle resources of type " + getContext().getResourceDefinition(theClass).getName());
}
return retVal;
}
private static void handleTransactionCreateOrUpdateOutcome(Map<IdDt, IdDt> idSubstitutions, Map<IdDt, DaoMethodOutcome> idToPersistedOutcome, IdDt nextResourceId, DaoMethodOutcome outcome, private static void handleTransactionCreateOrUpdateOutcome(Map<IdDt, IdDt> idSubstitutions, Map<IdDt, DaoMethodOutcome> idToPersistedOutcome, IdDt nextResourceId, DaoMethodOutcome outcome,
Entry newEntry, String theResourceType, IResource theRes) { Entry newEntry, String theResourceType, IResource theRes) {
IdDt newId = (IdDt) outcome.getId().toUnqualifiedVersionless(); IdDt newId = (IdDt) outcome.getId().toUnqualifiedVersionless();

View File

@ -587,12 +587,10 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
continue; continue;
} }
ourLog.info("Adding param: {}, {}", resourceName, nextValue.getValue()); ourLog.trace("Adding param: {}, {}", resourceName, nextValue.getValue());
ResourceIndexedSearchParamUri nextEntity = new ResourceIndexedSearchParamUri(resourceName, nextValue.getValue()); ResourceIndexedSearchParamUri nextEntity = new ResourceIndexedSearchParamUri(resourceName, nextValue.getValue());
ourLog.info("Added : {}", nextEntity);
nextEntity.setResource(theEntity); nextEntity.setResource(theEntity);
retVal.add(nextEntity); retVal.add(nextEntity);
} else { } else {

View File

@ -474,6 +474,17 @@ public class JsonParserDstu2Test {
assertThat(ourCtx.newJsonParser().setOmitResourceId(true).encodeResourceToString(p), not(containsString("123"))); assertThat(ourCtx.newJsonParser().setOmitResourceId(true).encodeResourceToString(p), not(containsString("123")));
} }
@Test
public void testParseAndEncodeBundleResourceWithComments() throws Exception {
String content = IOUtils.toString(JsonParserDstu2Test.class.getResourceAsStream("/bundle-transaction2.json"));
ourCtx.newJsonParser().parseBundle(content);
ca.uhn.fhir.model.dstu2.resource.Bundle parsed = ourCtx.newJsonParser().parseResource(ca.uhn.fhir.model.dstu2.resource.Bundle.class, content);
// TODO: preserve comments
}
@Test @Test
public void testParseAndEncodeBundle() throws Exception { public void testParseAndEncodeBundle() throws Exception {
String content = IOUtils.toString(JsonParserDstu2Test.class.getResourceAsStream("/bundle-example.json")); String content = IOUtils.toString(JsonParserDstu2Test.class.getResourceAsStream("/bundle-example.json"));

View File

@ -0,0 +1,348 @@
{
"resourceType": "Bundle",
"id": "bundle-transaction",
"meta": {
"fhir_comments": [
" this example bundle is a transaction ",
" when the transaction was constructed "
],
"lastUpdated": "2014-08-18T01:43:30Z"
},
"type": "transaction",
"entry": [
{
"fhir_comments": [
" now, each entry is an action to take in the transaction "
],
"fullUrl": "urn:uuid:61ebe359-bfdc-4613-8bf2-c5e300945f0a",
"resource": {
"resourceType": "Patient",
"text": {
"fhir_comments": [
" no id for create operations ",
" and no metadata on this resource, though it would be valid "
],
"status": "generated",
"div": "<div>Some narrative</div>"
},
"active": true,
"name": [
{
"use": "official",
"family": [
"Chalmers"
],
"given": [
"Peter",
"James"
]
}
],
"gender": "male",
"birthDate": "1974-12-25"
},
"request": {
"fhir_comments": [
" now, details about the action to take with the resource "
],
"method": "POST",
"_method": {
"fhir_comments": [
" POST to [base]/Patient - that's a create "
]
},
"url": "Patient",
"_url": {
"fhir_comments": [
" actually, in a transaction, you don't specify the [base], \n so [base]/Patient becomes just 'Patient': "
]
}
}
},
{
"fullUrl": "urn:uuid:88f151c0-a954-468a-88bd-5ae15c08e059",
"resource": {
"resourceType": "Patient",
"text": {
"fhir_comments": [
" no id for create operations ",
" and no metadata on this resource, though it would be valid "
],
"status": "generated",
"div": "<div>Some narrative</div>"
},
"active": true,
"name": [
{
"use": "official",
"family": [
"Chalmers"
],
"given": [
"Peter",
"James"
]
}
],
"gender": "male",
"birthDate": "1974-12-25"
},
"request": {
"fhir_comments": [
" transaction details "
],
"method": "POST",
"_method": {
"fhir_comments": [
" POST to [base]/Patient - that's a create "
]
},
"url": "Patient",
"_url": {
"fhir_comments": [
" actually, in a transaction, you don't specify the [base], \n so [base]/Patient becomes just 'Patient': "
]
},
"ifNoneExist": "identifier=234234",
"_ifNoneExist": {
"fhir_comments": [
" the conditional header: only add this resource if \n there isn't already one for this patient. If there is one,\n the content of this resource will be ignored "
]
}
}
},
{
"fullUrl": "http://example.org/fhir/Patient/123",
"resource": {
"resourceType": "Patient",
"id": "123",
"_id": {
"fhir_comments": [
" the id here and in the url have to match "
]
},
"text": {
"fhir_comments": [
" and no metadata on this resource, though it would be valid "
],
"status": "generated",
"div": "<div>Some narrative</div>"
},
"active": true,
"name": [
{
"use": "official",
"family": [
"Chalmers"
],
"given": [
"Peter",
"James"
]
}
],
"gender": "male",
"birthDate": "1974-12-25"
},
"request": {
"fhir_comments": [
" transaction details "
],
"method": "PUT",
"_method": {
"fhir_comments": [
" PUT to [base]/Patient/[id] - that's an update "
]
},
"url": "Patient/123",
"_url": {
"fhir_comments": [
" actually, in a transaction, you don't specify the [base], \n so [base]/Patient becomes just 'Patient': "
]
}
}
},
{
"fullUrl": "urn:uuid:74891afc-ed52-42a2-bcd7-f13d9b60f096",
"resource": {
"resourceType": "Patient",
"text": {
"fhir_comments": [
" no id for conditional update operations ",
" and no metadata on this resource, though it would be valid "
],
"status": "generated",
"div": "<div>Some narrative</div>"
},
"active": true,
"name": [
{
"use": "official",
"family": [
"Chalmers"
],
"given": [
"Peter",
"James"
]
}
],
"gender": "male",
"birthDate": "1974-12-25"
},
"request": {
"fhir_comments": [
" transaction details "
],
"method": "PUT",
"_method": {
"fhir_comments": [
" PUT to [base]/Patient?search_params - that's a conditional update "
]
},
"url": "Patient?identifier=234234",
"_url": {
"fhir_comments": [
" actually, in a transaction, you don't specify the [base], \n so [base]/Patient?params becomes just 'Patient?params': "
]
}
}
},
{
"fhir_comments": [
" a different kind of conditional update: version dependent "
],
"fullUrl": "http://example.org/fhir/Patient/123a",
"resource": {
"resourceType": "Patient",
"id": "123a",
"text": {
"status": "generated",
"div": "<div>Some narrative</div>"
},
"active": true,
"name": [
{
"use": "official",
"family": [
"Chalmers"
],
"given": [
"Peter",
"James"
]
}
],
"gender": "male",
"birthDate": "1974-12-25"
},
"request": {
"method": "PUT",
"url": "Patient/123a",
"ifMatch": "W/\"2\"",
"_ifMatch": {
"fhir_comments": [
" this will only succeed if the source version is correct: "
]
}
}
},
{
"request": {
"fhir_comments": [
" a simple delete operation ",
" no resource in this case ",
" transaction details "
],
"method": "DELETE",
"_method": {
"fhir_comments": [
" DELETE to [base]/Patient/[id]- that's a delete operation "
]
},
"url": "Patient/234",
"_url": {
"fhir_comments": [
" actually, in a transaction, you don't specify the [base], \n so [base]/Patient/234 becomes just 'Patient/234': ",
" btw, couldn't re-use Patient/123 for the delete, since \n the transaction couldn't do two different operations on the\n same resource "
]
}
}
},
{
"request": {
"fhir_comments": [
" a conditional delete operation ",
" no resource in this case ",
" transaction details "
],
"method": "DELETE",
"_method": {
"fhir_comments": [
" DELETE to [base]/Patient?params- that's a conditional delete operation "
]
},
"url": "Patient?identifier=123456",
"_url": {
"fhir_comments": [
" actually, in a transaction, you don't specify the [base], \n so [base]/Patient?params becomes just 'Patient?params': "
]
}
}
},
{
"fullUrl": "urn:uuid:79378cb8-8f58-48e8-a5e8-60ac2755b674",
"resource": {
"resourceType": "Parameters",
"parameter": [
{
"name": "coding",
"valueCoding": {
"system": "http://loinc.org",
"code": "1963-8"
}
}
]
},
"request": {
"method": "POST",
"_method": {
"fhir_comments": [
" POST to [base]/ValueSet/$lookup - invoking a lookup operation (see Terminology Service) "
]
},
"url": "ValueSet/$lookup"
}
},
{
"request": {
"fhir_comments": [
" can also do read-only operations. \n \n Note that these do not 'fail' - see discussion on transaction \n atomicity for further information \n "
],
"method": "GET",
"_method": {
"fhir_comments": [
" GET from [base]/Patient?params - searching for a patient "
]
},
"url": "Patient?name=peter"
}
},
{
"request": {
"fhir_comments": [
" and an example conditional read: "
],
"method": "GET",
"url": "Patient/12334",
"ifNoneMatch": "W/\"4\"",
"_ifNoneMatch": {
"fhir_comments": [
" in practice, you'd only specify one of these "
]
},
"ifModifiedSince": "2015-08-31T08:14:33+10:00"
}
}
]
}

View File

@ -11,6 +11,8 @@ import org.hl7.fhir.instance.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.instance.model.IdType; import org.hl7.fhir.instance.model.IdType;
import org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity; import org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity;
import org.hl7.fhir.instance.model.ValueSet; import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
@ -68,8 +70,8 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
} }
@Override @Override
public org.hl7.fhir.instance.utils.IWorkerContext.ValidationResult validateCode(String theCodeSystem, String theCode, String theDisplay) { public CodeValidationResult validateCode(String theCodeSystem, String theCode, String theDisplay) {
return new org.hl7.fhir.instance.utils.IWorkerContext.ValidationResult(IssueSeverity.INFORMATION, "Unknown code: " + theCodeSystem + " / " + theCode); return new CodeValidationResult(IssueSeverity.INFORMATION, "Unknown code: " + theCodeSystem + " / " + theCode);
} }
@Override @Override
@ -77,4 +79,9 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
return null; return null;
} }
@Override
public ValueSetExpansionComponent expandValueSet(ConceptSetComponent theInclude) {
return null;
}
} }

View File

@ -46,6 +46,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.validation.IValidationSupport.CodeValidationResult;
public class FhirInstanceValidator extends BaseValidatorBridge implements IValidatorModule { public class FhirInstanceValidator extends BaseValidatorBridge implements IValidatorModule {
@ -262,27 +263,13 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
private final class HapiWorkerContext implements IWorkerContext, ValueSetExpanderFactory, ValueSetExpander { private final class HapiWorkerContext implements IWorkerContext, ValueSetExpanderFactory, ValueSetExpander {
private final FhirContext myCtx; private final FhirContext myCtx;
// public StructureDefinition getProfile(String theId) {
// StructureDefinition retVal = super.getProfile(theId);
// if (retVal == null) {
// if (theId.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
// retVal = loadProfileOrReturnNull(null, getHl7OrgDstu2Ctx(myCtx),
// theId.substring("http://hl7.org/fhir/StructureDefinition/".length()));
// if (retVal != null) {
// seeProfile(theId, retVal);
// }
// }
// }
// return retVal;
// }
private HapiWorkerContext(FhirContext theCtx) { private HapiWorkerContext(FhirContext theCtx) {
myCtx = theCtx; myCtx = theCtx;
} }
@Override @Override
public ValueSetExpansionComponent expandVS(ConceptSetComponent theInc) { public ValueSetExpansionComponent expandVS(ConceptSetComponent theInc) {
throw new UnsupportedOperationException(); return myValidationSupport.expandValueSet(theInc);
} }
@Override @Override
@ -354,7 +341,11 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
@Override @Override
public ValidationResult validateCode(String theSystem, String theCode, String theDisplay) { public ValidationResult validateCode(String theSystem, String theCode, String theDisplay) {
return myValidationSupport.validateCode(theSystem, theCode, theDisplay); CodeValidationResult result = myValidationSupport.validateCode(theSystem, theCode, theDisplay);
if (result == null) {
return null;
}
return new ValidationResult(result.getSeverity(), result.getMessage(), result.asConceptDefinition());
} }
@Override @Override

View File

@ -25,121 +25,148 @@ import ca.uhn.fhir.util.ResourceReferenceInfo;
public class FhirQuestionnaireResponseValidator extends BaseValidatorBridge { public class FhirQuestionnaireResponseValidator extends BaseValidatorBridge {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirQuestionnaireResponseValidator.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory
private IResourceLoader myResourceLoader; .getLogger(FhirQuestionnaireResponseValidator.class);
private IResourceLoader myResourceLoader;
/** /**
* Set the class which will be used to load linked resources from the <code>QuestionnaireResponse</code>. Specifically, if the <code>QuestionnaireResponse</code> refers to an external (non-contained) * Set the class which will be used to load linked resources from the
* <code>Questionnaire</code>, or to any external (non-contained) <code>ValueSet</code>, the resource loader will be used to fetch those resources during the validation. * <code>QuestionnaireResponse</code>. Specifically, if the
* * <code>QuestionnaireResponse</code> refers to an external (non-contained)
* @param theResourceLoader * <code>Questionnaire</code>, or to any external (non-contained)
* The resourceloader to use. May be <code>null</code> if no resource loader should be used (in which case any <code>QuestionaireResponse</code> with external references will fail to * <code>ValueSet</code>, the resource loader will be used to fetch those
* validate.) * resources during the validation.
*/ *
public void setResourceLoader(IResourceLoader theResourceLoader) { * @param theResourceLoader
myResourceLoader = theResourceLoader; * The resourceloader to use. May be <code>null</code> if no resource
} * loader should be used (in which case any
* <code>QuestionaireResponse</code> with external references will
* fail to validate.)
*/
public void setResourceLoader(IResourceLoader theResourceLoader) {
myResourceLoader = theResourceLoader;
}
@Override @Override
protected List<ValidationMessage> validate(IValidationContext<?> theCtx) { protected List<ValidationMessage> validate(IValidationContext<?> theCtx) {
Object resource = theCtx.getResource(); Object resource = theCtx.getResource();
if (!(theCtx.getResource() instanceof IBaseResource)) { if (!(theCtx.getResource() instanceof IBaseResource)) {
ourLog.debug("Not validating object of type {}", theCtx.getResource().getClass()); ourLog.debug("Not validating object of type {}", theCtx.getResource().getClass());
return Collections.emptyList(); return Collections.emptyList();
} }
if (resource instanceof QuestionnaireResponse) { if (resource instanceof QuestionnaireResponse) {
return doValidate(theCtx, (QuestionnaireResponse) resource); return doValidate(theCtx, (QuestionnaireResponse) resource);
} }
RuntimeResourceDefinition def = theCtx.getFhirContext().getResourceDefinition((IBaseResource) resource); RuntimeResourceDefinition def = theCtx.getFhirContext().getResourceDefinition((IBaseResource) resource);
if ("QuestionnaireResponse".equals(def.getName()) == false) { if ("QuestionnaireResponse".equals(def.getName()) == false) {
return Collections.emptyList(); return Collections.emptyList();
} }
/* /*
* If we have a non-RI structure, convert it * If we have a non-RI structure, convert it
*/ */
IParser p = theCtx.getFhirContext().newJsonParser(); IParser p = theCtx.getFhirContext().newJsonParser();
String string = p.encodeResourceToString((IBaseResource) resource); String string = p.encodeResourceToString((IBaseResource) resource);
QuestionnaireResponse qa = p.parseResource(QuestionnaireResponse.class, string); QuestionnaireResponse qa = p.parseResource(QuestionnaireResponse.class, string);
return doValidate(theCtx, qa); return doValidate(theCtx, qa);
} }
private List<ValidationMessage> doValidate(IValidationContext<?> theValCtx, QuestionnaireResponse theResource) { private List<ValidationMessage> doValidate(IValidationContext<?> theValCtx, QuestionnaireResponse theResource) {
WorkerContext workerCtx = new WorkerContext(); WorkerContext workerCtx = new WorkerContext();
ArrayList<ValidationMessage> retVal = new ArrayList<ValidationMessage>(); ArrayList<ValidationMessage> retVal = new ArrayList<ValidationMessage>();
if (!loadReferences(theResource, workerCtx, theValCtx, retVal)) { if (!loadReferences(theResource, workerCtx, theValCtx, retVal)) {
return retVal; return retVal;
} }
QuestionnaireResponseValidator val = new QuestionnaireResponseValidator(workerCtx); QuestionnaireResponseValidator val = new QuestionnaireResponseValidator(workerCtx);
val.validate(retVal, theResource); val.validate(retVal, theResource);
return retVal; return retVal;
} }
private boolean loadReferences(IBaseResource theResource, WorkerContext theWorkerCtx, IValidationContext<?> theValCtx, ArrayList<ValidationMessage> theMessages) { private boolean loadReferences(IBaseResource theResource, WorkerContext theWorkerCtx, IValidationContext<?> theValCtx,
List<ResourceReferenceInfo> refs = theValCtx.getFhirContext().newTerser().getAllResourceReferences(theResource); ArrayList<ValidationMessage> theMessages) {
List<ResourceReferenceInfo> refs = theValCtx.getFhirContext().newTerser().getAllResourceReferences(theResource);
List<IBaseResource> newResources = new ArrayList<IBaseResource>(); List<IBaseResource> newResources = new ArrayList<IBaseResource>();
for (ResourceReferenceInfo nextRefInfo : refs) { for (ResourceReferenceInfo nextRefInfo : refs) {
IIdType nextRef = nextRefInfo.getResourceReference().getReferenceElement(); IIdType nextRef = nextRefInfo.getResourceReference().getReferenceElement();
String resourceType = nextRef.getResourceType(); String resourceType = nextRef.getResourceType();
if (isBlank(resourceType)) { if (nextRef.isLocal()) {
theMessages.add(new ValidationMessage(Source.QuestionnaireResponseValidator, org.hl7.fhir.instance.model.OperationOutcome.IssueType.INVALID, "Invalid reference '" + nextRef.getValue() + "' - Does not identify resource type", IssueSeverity.FATAL)); IBaseResource resource = nextRefInfo.getResourceReference().getResource();
} else if ("ValueSet".equals(resourceType)) { if (resource instanceof ValueSet) {
if (!theWorkerCtx.getValueSets().containsKey(nextRef.getValue())) { theWorkerCtx.getValueSets().put(nextRef.getValue(), (ValueSet) resource);
ValueSet resource = tryToLoad(ValueSet.class, nextRef, theMessages); newResources.add(resource);
if (resource == null) { } else if (resource instanceof Questionnaire) {
return false; theWorkerCtx.getQuestionnaires().put(nextRef.getValue(), (Questionnaire) resource);
} newResources.add(resource);
theWorkerCtx.getValueSets().put(nextRef.getValue(), resource); } else if (resource == null) {
newResources.add(resource); theMessages.add(new ValidationMessage(Source.QuestionnaireResponseValidator,
} org.hl7.fhir.instance.model.OperationOutcome.IssueType.INVALID,
} else if ("Questionnaire".equals(resourceType)) { "Invalid reference '" + nextRef.getValue() + "' - No contained resource with this ID found", IssueSeverity.FATAL));
if (!theWorkerCtx.getQuestionnaires().containsKey(nextRef.getValue())) { }
Questionnaire resource = tryToLoad(Questionnaire.class, nextRef, theMessages); } else if (isBlank(resourceType)) {
if (resource == null) { theMessages.add(new ValidationMessage(Source.QuestionnaireResponseValidator,
return false; org.hl7.fhir.instance.model.OperationOutcome.IssueType.INVALID,
} "Invalid reference '" + nextRef.getValue() + "' - Does not identify resource type", IssueSeverity.FATAL));
theWorkerCtx.getQuestionnaires().put(nextRef.getValue(), resource); } else if ("ValueSet".equals(resourceType)) {
newResources.add(resource); if (!theWorkerCtx.getValueSets().containsKey(nextRef.getValue())) {
} ValueSet resource = tryToLoad(ValueSet.class, nextRef, theMessages);
} if (resource == null) {
} return false;
}
theWorkerCtx.getValueSets().put(nextRef.getValue(), resource);
newResources.add(resource);
}
} else if ("Questionnaire".equals(resourceType)) {
if (!theWorkerCtx.getQuestionnaires().containsKey(nextRef.getValue())) {
Questionnaire resource = tryToLoad(Questionnaire.class, nextRef, theMessages);
if (resource == null) {
return false;
}
theWorkerCtx.getQuestionnaires().put(nextRef.getValue(), resource);
newResources.add(resource);
}
}
}
for (IBaseResource nextAddedResource : newResources) { for (IBaseResource nextAddedResource : newResources) {
boolean outcome = loadReferences(nextAddedResource, theWorkerCtx, theValCtx, theMessages); boolean outcome = loadReferences(nextAddedResource, theWorkerCtx, theValCtx, theMessages);
if (!outcome) { if (!outcome) {
return false; return false;
} }
} }
return true; return true;
} }
private <T extends IBaseResource> T tryToLoad(Class<T> theType, IIdType theReference, List<ValidationMessage> theMessages) { private <T extends IBaseResource> T tryToLoad(Class<T> theType, IIdType theReference,
if (myResourceLoader == null) { List<ValidationMessage> theMessages) {
theMessages.add(new ValidationMessage().setLevel(IssueSeverity.FATAL).setMessage("No resource loader present, could not load " + theReference)); if (myResourceLoader == null) {
return null; theMessages.add(new ValidationMessage().setLevel(IssueSeverity.FATAL)
} .setMessage("No resource loader present, could not load " + theReference));
return null;
}
try { try {
T retVal = myResourceLoader.load(theType, theReference); T retVal = myResourceLoader.load(theType, theReference);
if (retVal == null) { if (retVal == null) {
throw new IllegalStateException("ResourceLoader returned null. This is a bug with the resourceloader. Reference was: " + theReference); throw new IllegalStateException(
} "ResourceLoader returned null. This is a bug with the resourceloader. Reference was: " + theReference);
return retVal; }
} catch (ResourceNotFoundException e) { return retVal;
theMessages.add(new ValidationMessage().setLevel(IssueSeverity.FATAL).setMessage("Reference could not be found: " + theReference)); } catch (ResourceNotFoundException e) {
return null; theMessages.add(new ValidationMessage().setLevel(IssueSeverity.FATAL)
} .setMessage("Reference could not be found: " + theReference));
} return null;
}
}
} }

View File

@ -1,6 +1,10 @@
package ca.uhn.fhir.validation; package ca.uhn.fhir.validation;
import org.hl7.fhir.instance.model.ValueSet; import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity;
import org.hl7.fhir.instance.model.ValueSet.ConceptDefinitionComponent;
import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
@ -8,15 +12,22 @@ import ca.uhn.fhir.context.FhirContext;
public interface IValidationSupport { public interface IValidationSupport {
/** /**
* Returns <code>true</code> if codes in the given code system can be * Expands the given portion of a ValueSet
* validated *
* @param theInclude
* The portion to include
* @return The expansion
*/
ValueSetExpansionComponent expandValueSet(ConceptSetComponent theInclude);
/**
* Fetch a code system by ID
* *
* @param theSystem * @param theSystem
* The URI for the code system, e.g. <code>"http://loinc.org"</code> * The code system
* @return Returns <code>true</code> if codes in the given code system can be * @return The valueset (must not be null, but can be an empty ValueSet)
* validated
*/ */
boolean isCodeSystemSupported(String theSystem); ValueSet fetchCodeSystem(String theSystem);
/** /**
* Loads a resource needed by the validation (a StructureDefinition, or a * Loads a resource needed by the validation (a StructureDefinition, or a
@ -33,6 +44,17 @@ public interface IValidationSupport {
*/ */
<T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri); <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri);
/**
* Returns <code>true</code> if codes in the given code system can be expanded
* or validated
*
* @param theSystem
* The URI for the code system, e.g. <code>"http://loinc.org"</code>
* @return Returns <code>true</code> if codes in the given code system can be
* validated
*/
boolean isCodeSystemSupported(String theSystem);
/** /**
* Validates that the given code exists and if possible returns a display * 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" * name. This method is called to check codes which are found in "example"
@ -46,16 +68,48 @@ public interface IValidationSupport {
* The display name, if it should also be validated * The display name, if it should also be validated
* @return Returns a validation result object * @return Returns a validation result object
*/ */
org.hl7.fhir.instance.utils.IWorkerContext.ValidationResult validateCode(String theCodeSystem, String theCode, CodeValidationResult validateCode(String theCodeSystem, String theCode, String theDisplay);
String theDisplay);
/** public class CodeValidationResult {
* Fetch a code system by ID private ConceptDefinitionComponent definition;
* private String message;
* @param theSystem private IssueSeverity severity;
* The code system
* @return The valueset (must not be null, but can be an empty ValueSet) public CodeValidationResult(ConceptDefinitionComponent definition) {
*/ this.definition = definition;
ValueSet fetchCodeSystem(String theSystem); }
public CodeValidationResult(IssueSeverity severity, String message) {
this.severity = severity;
this.message = message;
}
public CodeValidationResult(IssueSeverity severity, String message, ConceptDefinitionComponent definition) {
this.severity = severity;
this.message = message;
this.definition = definition;
}
public ConceptDefinitionComponent asConceptDefinition() {
return definition;
}
public String getDisplay() {
return definition == null ? "??" : definition.getDisplay();
}
public String getMessage() {
return message;
}
public IssueSeverity getSeverity() {
return severity;
}
public boolean isOk() {
return definition != null;
}
}
} }

View File

@ -0,0 +1,73 @@
package ca.uhn.fhir.validation;
import java.util.Arrays;
import java.util.List;
import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
public class ValidationSupportChain implements IValidationSupport {
private List<IValidationSupport> myChain;
public ValidationSupportChain(IValidationSupport... theValidationSupportModules) {
myChain = Arrays.asList(theValidationSupportModules);
}
@Override
public ValueSetExpansionComponent expandValueSet(ConceptSetComponent theInclude) {
for (IValidationSupport next : myChain) {
if (next.isCodeSystemSupported(theInclude.getSystem())) {
return next.expandValueSet(theInclude);
}
}
return myChain.get(0).expandValueSet(theInclude);
}
@Override
public ValueSet fetchCodeSystem(String theSystem) {
for (IValidationSupport next : myChain) {
ValueSet retVal = next.fetchCodeSystem(theSystem);
if (retVal != null) {
return retVal;
}
}
return null;
}
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
for (IValidationSupport next : myChain) {
T retVal = next.fetchResource(theContext, theClass, theUri);
if (retVal != null) {
return retVal;
}
}
return null;
}
@Override
public boolean isCodeSystemSupported(String theSystem) {
for (IValidationSupport next : myChain) {
if (next.isCodeSystemSupported(theSystem)) {
return true;
}
}
return false;
}
@Override
public CodeValidationResult validateCode(String theCodeSystem, String theCode, String theDisplay) {
for (IValidationSupport next : myChain) {
if (next.isCodeSystemSupported(theCodeSystem)) {
return next.validateCode(theCodeSystem, theCode, theDisplay);
}
}
return myChain.get(0).validateCode(theCodeSystem, theCode, theDisplay);
}
}

View File

@ -54,7 +54,7 @@ import org.hl7.fhir.instance.utils.ToolingExtensions;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
public class ValueSetExpanderSimple implements ValueSetExpander { public class ValueSetExpanderSimple implements ValueSetExpander {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValueSetExpanderSimple.class);
private IWorkerContext context; private IWorkerContext context;
private List<ValueSetExpansionContainsComponent> codes = new ArrayList<ValueSet.ValueSetExpansionContainsComponent>(); private List<ValueSetExpansionContainsComponent> codes = new ArrayList<ValueSet.ValueSetExpansionContainsComponent>();
private Map<String, ValueSetExpansionContainsComponent> map = new HashMap<String, ValueSet.ValueSetExpansionContainsComponent>(); private Map<String, ValueSetExpansionContainsComponent> map = new HashMap<String, ValueSet.ValueSetExpansionContainsComponent>();
@ -88,6 +88,7 @@ public class ValueSetExpanderSimple implements ValueSetExpander {
} }
return new ValueSetExpansionOutcome(focus, null); return new ValueSetExpansionOutcome(focus, null);
} catch (Exception e) { } catch (Exception e) {
ourLog.error("Failure during expansion", e);
// well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set
// that might fail too, but it might not, later. // that might fail too, but it might not, later.
return new ValueSetExpansionOutcome(new ValueSetCheckerSimple(source, factory, context), e.getMessage()); return new ValueSetExpansionOutcome(new ValueSetCheckerSimple(source, factory, context), e.getMessage());

View File

@ -1,5 +1,7 @@
package org.hl7.fhir.instance.validation; package org.hl7.fhir.instance.validation;
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -282,7 +284,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(system), "Coding.system must be an absolute reference, not a local reference"); rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(system), "Coding.system must be an absolute reference, not a local reference");
if (system != null && code != null) { if (system != null && code != null) {
if (checkCode(errors, element, path, code, system, display)) if (checkCode(errors, element, path, code, system, display)) {
if (context != null && context.getBinding() != null) { if (context != null && context.getBinding() != null) {
ElementDefinitionBindingComponent binding = context.getBinding(); ElementDefinitionBindingComponent binding = context.getBinding();
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing")) { if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing")) {
@ -318,6 +320,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
} }
} }
}
} }
} }
@ -1746,44 +1749,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
// 5. inspect each child for validity // 5. inspect each child for validity
for (ElementInfo ei : children) { for (ElementInfo ei : children) {
if (ei.definition != null) { if (ei.definition != null) {
String type = null; String type = determineType(ei.definition, ei.name, ei.line(), ei.col(), errors, stack, profile);
ElementDefinition typeDefn = null; ElementDefinition typeDefn = null;
if (ei.definition.getType().size() == 1 && !ei.definition.getType().get(0).getCode().equals("*") && !ei.definition.getType().get(0).getCode().equals("Element") if (isBlank(type) && ei.definition.getNameReference() != null) {
&& !ei.definition.getType().get(0).getCode().equals("BackboneElement"))
type = ei.definition.getType().get(0).getCode();
else if (ei.definition.getType().size() == 1 && ei.definition.getType().get(0).getCode().equals("*")) {
String prefix = tail(ei.definition.getPath());
assert prefix.endsWith("[x]");
type = ei.name.substring(prefix.length() - 3);
if (isPrimitiveType(type))
type = Utilities.uncapitalize(type);
} else if (ei.definition.getType().size() > 1) {
String prefix = tail(ei.definition.getPath());
assert typesAreAllReference(ei.definition.getType()) || prefix.endsWith("[x]") : prefix;
prefix = prefix.substring(0, prefix.length() - 3);
for (TypeRefComponent t : ei.definition.getType())
if ((prefix + Utilities.capitalize(t.getCode())).equals(ei.name))
type = t.getCode();
if (type == null) {
TypeRefComponent trc = ei.definition.getType().get(0);
if (trc.getCode().equals("Reference"))
type = "Reference";
else
rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), false,
"The element " + ei.name + " is illegal. Valid types at this point are " + describeTypes(ei.definition.getType()));
}
} else if (ei.definition.getNameReference() != null) {
typeDefn = resolveNameReference(profile.getSnapshot(), ei.definition.getNameReference()); typeDefn = resolveNameReference(profile.getSnapshot(), ei.definition.getNameReference());
} }
if (type != null) {
if (type.startsWith("@")) {
ei.definition = findElement(profile, type.substring(1));
type = null;
}
}
NodeStack localStack = stack.push(ei.element, ei.count, ei.definition, type == null ? typeDefn : resolveType(type)); NodeStack localStack = stack.push(ei.element, ei.count, ei.definition, type == null ? typeDefn : resolveType(type));
String localStackLiterapPath = localStack.getLiteralPath(); String localStackLiterapPath = localStack.getLiteralPath();
String eiPath = ei.path; String eiPath = ei.path;
@ -1793,21 +1763,36 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (isPrimitiveType(type)) if (isPrimitiveType(type))
checkPrimitive(errors, ei.path, type, ei.definition, ei.element); checkPrimitive(errors, ei.path, type, ei.definition, ei.element);
else { else {
if (type.equals("Identifier")) if (type.equals("Identifier")) {
checkIdentifier(errors, ei.path, ei.element, ei.definition); checkIdentifier(errors, ei.path, ei.element, ei.definition);
else if (type.equals("Coding")) } else if (type.equals("Coding")) {
checkCoding(errors, ei.path, ei.element, profile, ei.definition); /*
else if (type.equals("CodeableConcept")) * We don't check coding children within a CodeableConcept, because
* checkCodeableConcept() already verifies the codings and the binding
* doesn't actually make it into checkCoding anyhow.
*/
boolean skip = false;
if (localStack.getParent() != null && localStack.getParent() != null) {
String parentType = determineType(localStack.getParent().getDefinition(), "", -1, -1, errors, stack, profile);
if ("CodeableConcept".equals(parentType)) {
skip = true;
}
}
if (!skip) {
checkCoding(errors, ei.path, ei.element, profile, ei.definition);
}
} else if (type.equals("CodeableConcept")) {
checkCodeableConcept(errors, ei.path, ei.element, profile, ei.definition); checkCodeableConcept(errors, ei.path, ei.element, profile, ei.definition);
else if (type.equals("Reference")) } else if (type.equals("Reference")) {
checkReference(errors, ei.path, ei.element, profile, ei.definition, actualType, localStack); checkReference(errors, ei.path, ei.element, profile, ei.definition, actualType, localStack);
}
if (type.equals("Extension"))
if (type.equals("Extension")) {
checkExtension(errors, ei.path, ei.element, ei.definition, profile, localStack); checkExtension(errors, ei.path, ei.element, ei.definition, profile, localStack);
else if (type.equals("Resource")) } else if (type.equals("Resource")) {
validateContains(errors, ei.path, ei.definition, definition, ei.element, localStack, !isBundleEntry(ei.path)); // if validateContains(errors, ei.path, ei.definition, definition, ei.element, localStack, !isBundleEntry(ei.path)); // if
// (str.matches(".*([.,/])work\\1$")) // (str.matches(".*([.,/])work\\1$"))
else { } else {
StructureDefinition p = getProfileForType(type); StructureDefinition p = getProfileForType(type);
if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.path, p != null, "Unknown type " + type)) { if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.path, p != null, "Unknown type " + type)) {
validateElement(errors, p, p.getSnapshot().getElement().get(0), profile, ei.definition, ei.element, type, localStack); validateElement(errors, p, p.getSnapshot().getElement().get(0), profile, ei.definition, ei.element, type, localStack);
@ -1822,6 +1807,50 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
} }
private String determineType(ElementDefinition definition, String name, int line, int col, List<ValidationMessage> theErrors, NodeStack theStack, StructureDefinition theProfile) {
String retVal = null;
if (definition.getType().size() == 1 && !definition.getType().get(0).getCode().equals("*") && !definition.getType().get(0).getCode().equals("Element")
&& !definition.getType().get(0).getCode().equals("BackboneElement")) {
retVal = definition.getType().get(0).getCode();
} else if (definition.getType().size() == 1 && definition.getType().get(0).getCode().equals("*")) {
String prefix = tail(definition.getPath());
assert prefix.endsWith("[x]");
retVal = name.substring(prefix.length() - 3);
if (isPrimitiveType(retVal)) {
retVal = Utilities.uncapitalize(retVal);
}
} else if (definition.getType().size() > 1) {
String prefix = tail(definition.getPath());
assert typesAreAllReference(definition.getType()) || prefix.endsWith("[x]") : prefix;
prefix = prefix.substring(0, prefix.length() - 3);
for (TypeRefComponent t : definition.getType()) {
if ((prefix + Utilities.capitalize(t.getCode())).equals(name)) {
retVal = t.getCode();
}
}
if (retVal == null) {
TypeRefComponent trc = definition.getType().get(0);
if (trc.getCode().equals("Reference")) {
retVal = "Reference";
} else {
rule(theErrors, IssueType.STRUCTURE, line, col, theStack.getLiteralPath(), false,
"The element " + name + " is illegal. Valid types at this point are " + describeTypes(definition.getType()));
}
}
}
if (retVal != null) {
if (retVal.startsWith("@")) {
definition = findElement(theProfile, retVal.substring(1));
retVal = null;
}
}
return retVal;
}
private void validateMessage(List<ValidationMessage> errors, WrapperElement bundle) { private void validateMessage(List<ValidationMessage> errors, WrapperElement bundle) {
// TODO Auto-generated method stub // TODO Auto-generated method stub
@ -2378,6 +2407,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return logicalPaths == null ? new ArrayList<String>() : logicalPaths; return logicalPaths == null ? new ArrayList<String>() : logicalPaths;
} }
public NodeStack getParent() {
return this.parent;
}
private ElementDefinition getType() { private ElementDefinition getType() {
return type; return type;
} }

View File

@ -9,7 +9,9 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.CodeType; import org.hl7.fhir.instance.model.CodeType;
@ -18,14 +20,16 @@ import org.hl7.fhir.instance.model.Observation.ObservationStatus;
import org.hl7.fhir.instance.model.StringType; import org.hl7.fhir.instance.model.StringType;
import org.hl7.fhir.instance.model.ValueSet; import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.ValueSet.ConceptDefinitionComponent; import org.hl7.fhir.instance.model.ValueSet.ConceptDefinitionComponent;
import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.validation.IValidationSupport.CodeValidationResult;
public class FhirInstanceValidatorTest { public class FhirInstanceValidatorTest {
@ -37,6 +41,7 @@ public class FhirInstanceValidatorTest {
private FhirValidator myVal; private FhirValidator myVal;
private ArrayList<String> myValidConcepts; private ArrayList<String> myValidConcepts;
private Map<String, ValueSetExpansionComponent> mySupportedCodeSystemsForExpansion;
private void addValidConcept(String theSystem, String theCode) { private void addValidConcept(String theSystem, String theCode) {
myValidConcepts.add(theSystem + "___" + theCode); myValidConcepts.add(theSystem + "___" + theCode);
@ -52,39 +57,57 @@ public class FhirInstanceValidatorTest {
myInstanceVal = new FhirInstanceValidator(); myInstanceVal = new FhirInstanceValidator();
myVal.registerValidatorModule(myInstanceVal); myVal.registerValidatorModule(myInstanceVal);
mySupportedCodeSystemsForExpansion = new HashMap<String, ValueSet.ValueSetExpansionComponent>();
myValidConcepts = new ArrayList<String>(); myValidConcepts = new ArrayList<String>();
myMockSupport = mock(IValidationSupport.class); myMockSupport = mock(IValidationSupport.class);
when(myMockSupport.expandValueSet(any(ConceptSetComponent.class))).thenAnswer(new Answer<ValueSetExpansionComponent>() {
@Override
public ValueSetExpansionComponent answer(InvocationOnMock theInvocation) throws Throwable {
ConceptSetComponent arg = (ConceptSetComponent)theInvocation.getArguments()[0];
ValueSetExpansionComponent retVal = mySupportedCodeSystemsForExpansion.get(arg.getSystem());
if (retVal == null) {
retVal = myDefaultValidationSupport.expandValueSet(arg);
}
ourLog.info("expandValueSet({}) : {}", new Object[] { theInvocation.getArguments()[0], retVal });
return retVal;
}
});
when(myMockSupport.isCodeSystemSupported(any(String.class))).thenAnswer(new Answer<Boolean>() { when(myMockSupport.isCodeSystemSupported(any(String.class))).thenAnswer(new Answer<Boolean>() {
@Override @Override
public Boolean answer(InvocationOnMock theInvocation) throws Throwable { public Boolean answer(InvocationOnMock theInvocation) throws Throwable {
boolean retVal = myDefaultValidationSupport.isCodeSystemSupported((String) theInvocation.getArguments()[0]); boolean retVal = mySupportedCodeSystemsForExpansion.containsKey(theInvocation.getArguments()[0]);
ourLog.info("isCodeSystemSupported({}) : {}", new Object[] { theInvocation.getArguments()[0], retVal }); ourLog.info("isCodeSystemSupported({}) : {}", new Object[] { theInvocation.getArguments()[0], retVal });
return retVal; return retVal;
} }
}); });
when(myMockSupport.fetchResource(any(FhirContext.class), any(Class.class), any(String.class))).thenAnswer(new Answer<IBaseResource>() { when(myMockSupport.fetchResource(any(FhirContext.class), any(Class.class), any(String.class)))
@Override .thenAnswer(new Answer<IBaseResource>() {
public IBaseResource answer(InvocationOnMock theInvocation) throws Throwable {
IBaseResource retVal = myDefaultValidationSupport.fetchResource((FhirContext) theInvocation.getArguments()[0],
(Class<IBaseResource>) theInvocation.getArguments()[1], (String) theInvocation.getArguments()[2]);
ourLog.info("fetchResource({}, {}) : {}", new Object[] { theInvocation.getArguments()[1], theInvocation.getArguments()[2], retVal });
return retVal;
}
});
when(myMockSupport.validateCode(any(String.class), any(String.class), any(String.class)))
.thenAnswer(new Answer<org.hl7.fhir.instance.utils.IWorkerContext.ValidationResult>() {
@Override @Override
public org.hl7.fhir.instance.utils.IWorkerContext.ValidationResult answer(InvocationOnMock theInvocation) throws Throwable { public IBaseResource answer(InvocationOnMock theInvocation) throws Throwable {
IBaseResource retVal = myDefaultValidationSupport.fetchResource(
(FhirContext) theInvocation.getArguments()[0], (Class<IBaseResource>) theInvocation.getArguments()[1],
(String) theInvocation.getArguments()[2]);
ourLog.info("fetchResource({}, {}) : {}",
new Object[] { theInvocation.getArguments()[1], theInvocation.getArguments()[2], retVal });
return retVal;
}
});
when(myMockSupport.validateCode(any(String.class), any(String.class), any(String.class)))
.thenAnswer(new Answer<CodeValidationResult>() {
@Override
public CodeValidationResult answer(InvocationOnMock theInvocation) throws Throwable {
String system = (String) theInvocation.getArguments()[0]; String system = (String) theInvocation.getArguments()[0];
String code = (String) theInvocation.getArguments()[1]; String code = (String) theInvocation.getArguments()[1];
org.hl7.fhir.instance.utils.IWorkerContext.ValidationResult retVal; CodeValidationResult retVal;
if (myValidConcepts.contains(system + "___" + code)) { if (myValidConcepts.contains(system + "___" + code)) {
retVal = new org.hl7.fhir.instance.utils.IWorkerContext.ValidationResult(new ConceptDefinitionComponent(new CodeType(code))); retVal = new CodeValidationResult(new ConceptDefinitionComponent(new CodeType(code)));
} else { } else {
retVal = myDefaultValidationSupport.validateCode(system, code, (String) theInvocation.getArguments()[2]); retVal = myDefaultValidationSupport.validateCode(system, code, (String) theInvocation.getArguments()[2]);
} }
ourLog.info("validateCode({}, {}, {}) : {}", new Object[] { system, code, (String) theInvocation.getArguments()[2], retVal }); ourLog.info("validateCode({}, {}, {}) : {}",
new Object[] { system, code, (String) theInvocation.getArguments()[2], retVal });
return retVal; return retVal;
} }
}); });
@ -104,7 +127,8 @@ public class FhirInstanceValidatorTest {
int index = 0; int index = 0;
for (SingleValidationMessage next : theOutput.getMessages()) { for (SingleValidationMessage next : theOutput.getMessages()) {
ourLog.info("Result {}: {} - {} - {}", new Object[] { index, next.getSeverity(), next.getLocationString(), next.getMessage() }); ourLog.info("Result {}: {} - {} - {}",
new Object[] { index, next.getSeverity(), next.getLocationString(), next.getMessage() });
index++; index++;
if (next.getSeverity() != ResultSeverityEnum.INFORMATION) { if (next.getSeverity() != ResultSeverityEnum.INFORMATION) {
@ -115,6 +139,21 @@ public class FhirInstanceValidatorTest {
return retVal; return retVal;
} }
private List<SingleValidationMessage> logResultsAndReturnAll(ValidationResult theOutput) {
List<SingleValidationMessage> retVal = new ArrayList<SingleValidationMessage>();
int index = 0;
for (SingleValidationMessage next : theOutput.getMessages()) {
ourLog.info("Result {}: {} - {} - {}",
new Object[] { index, next.getSeverity(), next.getLocationString(), next.getMessage() });
index++;
retVal.add(next);
}
return retVal;
}
@Test @Test
public void testValidateRawJsonResource() { public void testValidateRawJsonResource() {
// @formatter:off // @formatter:off
@ -152,7 +191,8 @@ public class FhirInstanceValidatorTest {
@Test @Test
public void testValidateRawXmlResourceBadAttributes() { public void testValidateRawXmlResourceBadAttributes() {
// @formatter:off // @formatter:off
String input = "<Patient xmlns=\"http://hl7.org/fhir\">" + "<id value=\"123\"/>" + "<foo value=\"222\"/>" + "</Patient>"; String input = "<Patient xmlns=\"http://hl7.org/fhir\">" + "<id value=\"123\"/>" + "<foo value=\"222\"/>"
+ "</Patient>";
// @formatter:on // @formatter:on
ValidationResult output = myVal.validateWithResult(input); ValidationResult output = myVal.validateWithResult(input);
@ -173,7 +213,8 @@ public class FhirInstanceValidatorTest {
ValidationResult output = myVal.validateWithResult(input); ValidationResult output = myVal.validateWithResult(input);
assertThat(output.getMessages().size(), greaterThan(0)); assertThat(output.getMessages().size(), greaterThan(0));
assertEquals("Element '/f:Observation.status': minimum required = 1, but only found 0", output.getMessages().get(0).getMessage()); assertEquals("Element '/f:Observation.status': minimum required = 1, but only found 0",
output.getMessages().get(0).getMessage());
} }
@ -181,9 +222,9 @@ public class FhirInstanceValidatorTest {
* See #216 * See #216
*/ */
@Test @Test
@Ignore
public void testValidateRawXmlInvalidChoiceName() throws Exception { public void testValidateRawXmlInvalidChoiceName() throws Exception {
String input = IOUtils.toString(FhirInstanceValidator.class.getResourceAsStream("/medicationstatement_invalidelement.xml")); String input = IOUtils
.toString(FhirInstanceValidator.class.getResourceAsStream("/medicationstatement_invalidelement.xml"));
ValidationResult output = myVal.validateWithResult(input); ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> res = logResultsAndReturnNonInformationalOnes(output); List<SingleValidationMessage> res = logResultsAndReturnNonInformationalOnes(output);
@ -205,7 +246,8 @@ public class FhirInstanceValidatorTest {
ValidationResult output = myVal.validateWithResult(input); ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output); List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertEquals(errors.toString(), 1, errors.size()); assertEquals(errors.toString(), 1, errors.size());
assertEquals("StructureDefinition reference \"http://foo/myprofile\" could not be resolved", errors.get(0).getMessage()); assertEquals("StructureDefinition reference \"http://foo/myprofile\" could not be resolved",
errors.get(0).getMessage());
} }
@Test @Test
@ -224,9 +266,11 @@ public class FhirInstanceValidatorTest {
ValidationResult output = myVal.validateWithResult(input); ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output); List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertThat(errors.toString(), containsString("Element '/f:Observation.subject': minimum required = 1, but only found 0")); assertThat(errors.toString(),
containsString("Element '/f:Observation.subject': minimum required = 1, but only found 0"));
assertThat(errors.toString(), containsString("Element encounter @ /f:Observation: max allowed = 0, but found 1")); assertThat(errors.toString(), containsString("Element encounter @ /f:Observation: max allowed = 0, but found 1"));
assertThat(errors.toString(), containsString("Element '/f:Observation.device': minimum required = 1, but only found 0")); assertThat(errors.toString(),
containsString("Element '/f:Observation.device': minimum required = 1, but only found 0"));
assertThat(errors.toString(), containsString("")); assertThat(errors.toString(), containsString(""));
} }
@ -245,8 +289,8 @@ public class FhirInstanceValidatorTest {
@Test @Test
public void testValidateResourceWithDefaultValuesetBadCode() { public void testValidateResourceWithDefaultValuesetBadCode() {
String input = "<Observation xmlns=\"http://hl7.org/fhir\">\n" + " <status value=\"notvalidcode\"/>\n" + " <code>\n" String input = "<Observation xmlns=\"http://hl7.org/fhir\">\n" + " <status value=\"notvalidcode\"/>\n"
+ " <text value=\"No code here!\"/>\n" + " </code>\n" + "</Observation>"; + " <code>\n" + " <text value=\"No code here!\"/>\n" + " </code>\n" + "</Observation>";
ValidationResult output = myVal.validateWithResult(input); ValidationResult output = myVal.validateWithResult(input);
assertEquals( assertEquals(
"Coded value notvalidcode is not in value set http://hl7.org/fhir/ValueSet/observation-status (http://hl7.org/fhir/ValueSet/observation-status)", "Coded value notvalidcode is not in value set http://hl7.org/fhir/ValueSet/observation-status (http://hl7.org/fhir/ValueSet/observation-status)",
@ -270,7 +314,7 @@ public class FhirInstanceValidatorTest {
} }
@Test @Test
public void testValidateResourceWithExampleBindingCodeValidationPassing() { public void testValidateResourceWithExampleBindingCodeValidationPassingLoinc() {
Observation input = new Observation(); Observation input = new Observation();
myInstanceVal.setValidationSupport(myMockSupport); myInstanceVal.setValidationSupport(myMockSupport);
@ -283,4 +327,39 @@ public class FhirInstanceValidatorTest {
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output); List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertEquals(errors.toString(), 0, errors.size()); assertEquals(errors.toString(), 0, errors.size());
} }
@Test
public void testValidateResourceWithExampleBindingCodeValidationPassingLoincWithExpansion() {
Observation input = new Observation();
ValueSetExpansionComponent expansionComponent = new ValueSetExpansionComponent();
expansionComponent.addContains().setSystem("http://loinc.org").setCode("12345").setDisplay("Some display code");
mySupportedCodeSystemsForExpansion.put("http://loinc.org", expansionComponent);
myInstanceVal.setValidationSupport(myMockSupport);
addValidConcept("http://loinc.org", "12345");
input.setStatus(ObservationStatus.FINAL);
input.getCode().addCoding().setSystem("http://loinc.org").setCode("1234");
ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertEquals(errors.toString(), 0, errors.size());
}
@Test
public void testValidateResourceWithExampleBindingCodeValidationPassingNonLoinc() {
Observation input = new Observation();
myInstanceVal.setValidationSupport(myMockSupport);
addValidConcept("http://acme.org", "12345");
input.setStatus(ObservationStatus.FINAL);
input.getCode().addCoding().setSystem("http://acme.org").setCode("12345");
ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> errors = logResultsAndReturnAll(output);
assertEquals(errors.toString(), 0, errors.size());
}
} }

View File

@ -188,6 +188,30 @@ public class QuestionnaireResponseValidatorIntegrationTest {
assertThat(result.getMessages().toString(), containsString("myMessage=Reference could not be found: http://some")); assertThat(result.getMessages().toString(), containsString("myMessage=Reference could not be found: http://some"));
} }
@Test
public void testContainedQuestionnaireAndValueSet() {
ValueSet vs = new ValueSet();
vs.getCodeSystem().setSystem("urn:system").addConcept().setCode("code1");
vs.setId("#VVV");
Questionnaire q = new Questionnaire();
q.setId("#QQQ");
Reference vsRef = new Reference("#VVV");
vsRef.setResource(vs);
q.getGroup().addQuestion().setLinkId("link0").setRequired(false).setType(AnswerFormat.CHOICE).setOptions(vsRef);
QuestionnaireResponse qa;
// Bad code
qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference("#QQQ");
qa.getQuestionnaire().setResource(q);
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system").setCode("code1"));
ValidationResult result = myVal.validateWithResult(qa);
assertEquals(result.getMessages().toString(), 0, result.getMessages().size());
}
/** /**
* Sample provided by Eric van der Zwan * Sample provided by Eric van der Zwan
*/ */

58
pom.xml
View File

@ -212,8 +212,9 @@
<apache_httpclient_version>4.4</apache_httpclient_version> <apache_httpclient_version>4.4</apache_httpclient_version>
<apache_httpcore_version>4.4</apache_httpcore_version> <apache_httpcore_version>4.4</apache_httpcore_version>
<derby_version>10.11.1.1</derby_version> <derby_version>10.11.1.1</derby_version>
<!-- Note on Hibernate versions: Hibernate 4.3+ uses JPA 2.1, which is too new for a number of platforms including JBoss EAP 6.x and Glassfish 3.0. Upgrade this version with caution! Also note that if <!-- Note on Hibernate versions: Hibernate 4.3+ uses JPA 2.1, which is too new for a number of platforms including JBoss EAP 6.x and Glassfish 3.0. Upgrade this
you change this, you may get a failure in hibernate4-maven-plugin. See the note in hapi-fhir-jpaserver-base/pom.xml's configuration for that plugin... <hibernate_version>4.3.7.Final</hibernate_version> --> version with caution! Also note that if you change this, you may get a failure in hibernate4-maven-plugin. See the note in hapi-fhir-jpaserver-base/pom.xml's configuration
for that plugin... <hibernate_version>4.3.7.Final</hibernate_version> -->
<hibernate_version>4.2.17.Final</hibernate_version> <hibernate_version>4.2.17.Final</hibernate_version>
<hibernate_validator_version>5.1.0.Final</hibernate_validator_version> <hibernate_validator_version>5.1.0.Final</hibernate_validator_version>
<jetty_version>9.2.6.v20141205</jetty_version> <jetty_version>9.2.6.v20141205</jetty_version>
@ -429,6 +430,11 @@
<artifactId>jetty-webapp</artifactId> <artifactId>jetty-webapp</artifactId>
<version>9.2.6.v20141205</version> <version>9.2.6.v20141205</version>
</dependency> </dependency>
<dependency>
<groupId>org.fusesource.jansi</groupId>
<artifactId>jansi</artifactId>
<version>1.11</version>
</dependency>
<dependency> <dependency>
<groupId>org.glassfish</groupId> <groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId> <artifactId>javax.el</artifactId>
@ -833,43 +839,43 @@
<configuration> <configuration>
<target> <target>
<copy todir="target/site/apidocs"> <copy todir="target/site/apidocs">
<fileset dir="hapi-fhir-base/target/site/apidocs"/> <fileset dir="hapi-fhir-base/target/site/apidocs" />
</copy> </copy>
<copy todir="target/site/apidocs-dstu"> <copy todir="target/site/apidocs-dstu">
<fileset dir="hapi-fhir-structures-dstu/target/site/apidocs"/> <fileset dir="hapi-fhir-structures-dstu/target/site/apidocs" />
</copy> </copy>
<copy todir="target/site/apidocs-hl7org-dstu2"> <copy todir="target/site/apidocs-hl7org-dstu2">
<fileset dir="hapi-fhir-structures-hl7org-dstu2/target/site/apidocs"/> <fileset dir="hapi-fhir-structures-hl7org-dstu2/target/site/apidocs" />
</copy> </copy>
<copy todir="target/site/apidocs-dstu2"> <copy todir="target/site/apidocs-dstu2">
<fileset dir="hapi-fhir-structures-dstu2/target/site/apidocs"/> <fileset dir="hapi-fhir-structures-dstu2/target/site/apidocs" />
</copy> </copy>
<copy todir="target/site/apidocs-jpaserver"> <copy todir="target/site/apidocs-jpaserver">
<fileset dir="hapi-fhir-jpaserver-base/target/site/apidocs"/> <fileset dir="hapi-fhir-jpaserver-base/target/site/apidocs" />
</copy> </copy>
<copy todir="target/site/xref-jpaserver"> <copy todir="target/site/xref-jpaserver">
<fileset dir="hapi-fhir-jpaserver-base/target/site/xref"/> <fileset dir="hapi-fhir-jpaserver-base/target/site/xref" />
</copy> </copy>
<copy todir="target/site/xref-base"> <copy todir="target/site/xref-base">
<fileset dir="hapi-fhir-base/target/site/xref"/> <fileset dir="hapi-fhir-base/target/site/xref" />
</copy> </copy>
<!-- <copy todir="target/site/cobertura"> <fileset dir="hapi-fhir-cobertura/target/site/cobertura" /> </copy> --> <!-- <copy todir="target/site/cobertura"> <fileset dir="hapi-fhir-cobertura/target/site/cobertura" /> </copy> -->
<copy todir="target/site"> <copy todir="target/site">
<fileset dir="hapi-fhir-base/target/site" includes="checkstyle.*"/> <fileset dir="hapi-fhir-base/target/site" includes="checkstyle.*" />
</copy> </copy>
<echo>Fixing Checkstyle Report</echo> <echo>Fixing Checkstyle Report</echo>
<replace dir="target/site" summary="true"> <replace dir="target/site" summary="true">
<include name="checkstyle.html"/> <include name="checkstyle.html" />
<replacetoken>"../../</replacetoken> <replacetoken>"../../</replacetoken>
<replacevalue>"./</replacevalue> <replacevalue>"./</replacevalue>
</replace> </replace>
<replace dir="target/site" summary="true"> <replace dir="target/site" summary="true">
<include name="*.html"/> <include name="*.html" />
<replacetoken>http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-responsive.min.css</replacetoken> <replacetoken>http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-responsive.min.css</replacetoken>
<replacevalue>./css/bootstrap-responsive.min.css</replacevalue> <replacevalue>./css/bootstrap-responsive.min.css</replacevalue>
</replace> </replace>
<replace dir="target/site" summary="true"> <replace dir="target/site" summary="true">
<include name="index.html"/> <include name="index.html" />
<replacetoken><![CDATA[<h2 id="Welcome">Welcome</h2>]]></replacetoken> <replacetoken><![CDATA[<h2 id="Welcome">Welcome</h2>]]></replacetoken>
<replacevalue><![CDATA[<div class="jumbotron subhead"> <replacevalue><![CDATA[<div class="jumbotron subhead">
<div class="row" id="banner"> <div class="row" id="banner">
@ -898,33 +904,33 @@
<target> <target>
<echo>Adding Fontawesome</echo> <echo>Adding Fontawesome</echo>
<replace dir="target/site" summary="true"> <replace dir="target/site" summary="true">
<include name="*.html"/> <include name="*.html" />
<replacetoken><![CDATA[<a href="download.html" title="Download">Download</a>]]></replacetoken> <replacetoken><![CDATA[<a href="download.html" title="Download">Download</a>]]></replacetoken>
<replacevalue><![CDATA[<a href="download.html" title="Download"><i class="fa fa-download"></i> Download</a>]]></replacevalue> <replacevalue><![CDATA[<a href="download.html" title="Download"><i class="fa fa-download"></i> Download</a>]]></replacevalue>
</replace> </replace>
<replace dir="target/site" summary="true"> <replace dir="target/site" summary="true">
<include name="*.html"/> <include name="*.html" />
<replacetoken><![CDATA[<a href="https://github.com/jamesagnew/hapi-fhir/" title="GitHub Project" class="externalLink">GitHub Project</a>]]></replacetoken> <replacetoken><![CDATA[<a href="https://github.com/jamesagnew/hapi-fhir/" title="GitHub Project" class="externalLink">GitHub Project</a>]]></replacetoken>
<replacevalue><![CDATA[<a href="https://github.com/jamesagnew/hapi-fhir/" title="GitHub Project" class="externalLink"><i class="fa fa-github"></i> GitHub Project</a>]]></replacevalue> <replacevalue><![CDATA[<a href="https://github.com/jamesagnew/hapi-fhir/" title="GitHub Project" class="externalLink"><i class="fa fa-github"></i> GitHub Project</a>]]></replacevalue>
</replace> </replace>
<replace dir="target/site" summary="true"> <replace dir="target/site" summary="true">
<include name="*.html"/> <include name="*.html" />
<replacetoken><![CDATA[data-toggle="dropdown">Test Servers <]]></replacetoken> <replacetoken><![CDATA[data-toggle="dropdown">Test Servers <]]></replacetoken>
<replacevalue><![CDATA[data-toggle="dropdown"><i class="fa fa-fire"></i>&nbsp;Test Servers&nbsp;<]]></replacevalue> <replacevalue><![CDATA[data-toggle="dropdown"><i class="fa fa-fire"></i>&nbsp;Test Servers&nbsp;<]]></replacevalue>
</replace> </replace>
<replace dir="target/site" summary="true"> <replace dir="target/site" summary="true">
<include name="*.html"/> <include name="*.html" />
<replacetoken><![CDATA[data-toggle="dropdown">Documentation <]]></replacetoken> <replacetoken><![CDATA[data-toggle="dropdown">Documentation <]]></replacetoken>
<replacevalue><![CDATA[data-toggle="dropdown"><i class="fa fa-book"></i>&nbsp;Documentation&nbsp;<]]></replacevalue> <replacevalue><![CDATA[data-toggle="dropdown"><i class="fa fa-book"></i>&nbsp;Documentation&nbsp;<]]></replacevalue>
</replace> </replace>
<replace dir="target/site" summary="true"> <replace dir="target/site" summary="true">
<include name="*.html"/> <include name="*.html" />
<replacetoken><![CDATA[data-toggle="dropdown">Get Help <]]></replacetoken> <replacetoken><![CDATA[data-toggle="dropdown">Get Help <]]></replacetoken>
<replacevalue><![CDATA[data-toggle="dropdown"><i class="fa fa-support"></i>&nbsp;Get Help&nbsp;<]]></replacevalue> <replacevalue><![CDATA[data-toggle="dropdown"><i class="fa fa-support"></i>&nbsp;Get Help&nbsp;<]]></replacevalue>
</replace> </replace>
<echo>Changing Breadcrumbs</echo> <echo>Changing Breadcrumbs</echo>
<replace dir="target/site" summary="true"> <replace dir="target/site" summary="true">
<include name="doc_*.html"/> <include name="doc_*.html" />
<replacetoken><![CDATA[<li class="divider">/</li>]]></replacetoken> <replacetoken><![CDATA[<li class="divider">/</li>]]></replacetoken>
<replacevalue><![CDATA[<li class="divider">/</li> <replacevalue><![CDATA[<li class="divider">/</li>
<li><a href="docindex.html" title="Documentation">Documentation</a></li> <li><a href="docindex.html" title="Documentation">Documentation</a></li>
@ -975,8 +981,8 @@
<echo>Adding Google analytics in target/site for &lt;body&gt;</echo> <echo>Adding Google analytics in target/site for &lt;body&gt;</echo>
<replace dir="target/site" summary="true"> <replace dir="target/site" summary="true">
<include name="**/*.html"></include> <include name="**/*.html"></include>
<replacefilter token="#build#" value="${label}"/> <replacefilter token="#build#" value="${label}" />
<replacefilter token="#version#" value="${project.version}"/> <replacefilter token="#version#" value="${project.version}" />
<replacetoken><![CDATA[</body>]]></replacetoken> <replacetoken><![CDATA[</body>]]></replacetoken>
<replacevalue><![CDATA[ <replacevalue><![CDATA[
<script> <script>
@ -1114,8 +1120,9 @@
<reporting> <reporting>
<plugins> <plugins>
<!-- <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-checkstyle-plugin</artifactId> <reportSets> <reportSet> <reports><report>checkstyle-aggregate</report></reports> </reportSet> <!-- <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-checkstyle-plugin</artifactId> <reportSets> <reportSet> <reports><report>checkstyle-aggregate</report></reports>
</reportSets> <configuration> <configLocation>config/sun_checks.xml</configLocation> <includes> hapi-fhir-base/src/main/java/**/*.java </includes> </configuration> </plugin> --> </reportSet> </reportSets> <configuration> <configLocation>config/sun_checks.xml</configLocation> <includes> hapi-fhir-base/src/main/java/**/*.java </includes> </configuration>
</plugin> -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-changes-plugin</artifactId> <artifactId>maven-changes-plugin</artifactId>
@ -1190,8 +1197,9 @@
</modules> </modules>
<build> <build>
<plugins> <plugins>
<!-- <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>${maven_assembly_plugin_version}</version> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> <!-- <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>${maven_assembly_plugin_version}</version> <executions> <execution> <phase>package</phase>
<configuration> <attach>false</attach> <descriptors> <descriptor>${project.basedir}/src/assembly/hapi-fhir-sample-projects.xml</descriptor> </descriptors> </configuration> </execution> </executions> </plugin> --> <goals> <goal>single</goal> </goals> <configuration> <attach>false</attach> <descriptors> <descriptor>${project.basedir}/src/assembly/hapi-fhir-sample-projects.xml</descriptor>
</descriptors> </configuration> </execution> </executions> </plugin> -->
</plugins> </plugins>
</build> </build>

View File

@ -181,6 +181,12 @@
definitions provided either by HL7 or by the user. definitions provided either by HL7 or by the user.
</p> </p>
<p class="doc_info_bubble">
This style of validation is still experimental, and should be used with caution.
It is very powerful, but is still under active development and may continue to
change over time.
</p>
<subsection name="Preparation"> <subsection name="Preparation">
<p> <p>