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.filefilter.WildcardFileFilter;
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;
@ -21,6 +23,8 @@ import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.client.IGenericClient;
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.FhirValidator;
import ca.uhn.fhir.validation.IValidationSupport;
@ -172,7 +176,7 @@ public class ValidatorExamples {
IValidationSupport valSupport = new IValidationSupport() {
@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
return null;
}
@ -194,8 +198,23 @@ public class ValidatorExamples {
// TODO: Implement
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
}

View File

@ -1101,7 +1101,9 @@ public class JsonParser extends BaseParser implements IParser {
continue;
}
// _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;
} else if ("extension".equals(nextName)) {
JsonArray array = theObject.getJsonArray(nextName);

View File

@ -1076,6 +1076,8 @@ class ParserState<T> {
} else if ("fullUrl".equals(theLocalPart)) {
myFullUrl = new IdDt();
push(new PrimitiveState(getPreResourceState(), myFullUrl));
} else if ("fhir_comments".equals(theLocalPart) && myJsonMode) {
push(new SwallowChildrenWholeState(getPreResourceState()));
} else {
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">
<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
if you are using this file as a basis for your own project. -->
<!-- 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 if you are using this file as a basis for your own project. -->
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
@ -30,6 +30,11 @@
<version>1.2-SNAPSHOT</version>
<type>war</type>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu</artifactId>
<version>1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu2</artifactId>
@ -91,7 +96,34 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
</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>
<build>
@ -163,6 +195,35 @@
</executions>
</plugin>
</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>
</project>

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.cli;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -7,26 +8,130 @@ import java.util.List;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
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 {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(App.class);
private static List<BaseCommand> ourCommands;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(App.class);
static {
ourCommands = new ArrayList<BaseCommand>();
ourCommands.add(new RunServerCommand());
ourCommands.add(new ExampleDataUploader());
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) {
loggingConfigOff();
AnsiConsole.systemInstall();
// log version while the logging is off
VersionUtil.getVersion();
if (theArgs.length == 0) {
logUsage();
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;
for (BaseCommand nextCommand : ourCommands) {
if (nextCommand.getCommandName().equals(theArgs[0])) {
@ -41,18 +146,24 @@ public class App {
try {
String[] args = Arrays.asList(theArgs).subList(1, theArgs.length).toArray(new String[theArgs.length - 1]);
parsedOptions = parser.parse(options, args, true);
logAppHeader();
loggingConfigOn();
// Actually execute the command
command.run(parsedOptions);
} catch (ParseException e) {
ourLog.error("Invalid command options for command: " + command.getCommandName());
ourLog.error(e.getMessage());
ourLog.error("Aborting!");
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();
}
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
public int compareTo(BaseCommand theO) {
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;
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
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.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
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.Entry;
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.valueset.HTTPVerbEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.util.ResourceReferenceInfo;
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);
public static void main(String[] args) throws Exception {
new ExampleDataUploader().execute();
@Override
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 {
ourLog.info("Starting...");
@Override
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();
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();
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());
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));
byte[] buffer = new byte[2048];
@ -69,6 +114,10 @@ public class ExampleDataUploader extends BaseCommand {
byte[] exampleBytes = bos.toByteArray();
String exampleString = new String(exampleBytes, "UTF-8");
if (ourLog.isTraceEnabled()) {
ourLog.trace("Next example: " + exampleString);
}
IBaseResource parsed;
try {
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++) {
Entry next = bundle.getEntry().get(i);
if (next.getResource().getId().getIdPart() != null) {
String nextId = next.getResource().getResourceName() + '/' + next.getResource().getId().getIdPart();
if (!ids.add(nextId)) {
String idPart = next.getResource().getId().getIdPart();
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);
bundle.getEntry().remove(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());
for (ResourceReferenceInfo nextRef : refs) {
// 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(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);
nextRef.getResourceReference().getReferenceElement().setValue(null);
}
} else {
goodRefs++;
}
@ -134,8 +233,10 @@ public class ExampleDataUploader extends BaseCommand {
}
// for (Entry next : bundle.getEntry()) {
// if (next.getResource().getId().hasIdPart() && Character.isLetter(next.getResource().getId().getIdPart().charAt(0))) {
// next.getTransaction().setUrl(next.getResource().getResourceName() + '/' + next.getResource().getId().getIdPart());
// if (next.getResource().getId().hasIdPart() &&
// Character.isLetter(next.getResource().getId().getIdPart().charAt(0))) {
// next.getTransaction().setUrl(next.getResource().getResourceName() + '/' +
// next.getResource().getId().getIdPart());
// 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: {} 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();
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();
root.setAllowDuplicateFragmentNames(true);
root.setWar(tempWarFile.getAbsolutePath());
root.setParentLoaderPriority(true);
root.setContextPath("/");
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.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.http.client.ClientProtocolException;
import org.hl7.fhir.instance.model.Bundle;
@ -29,7 +32,7 @@ public class ValidationDataUploader extends BaseCommand {
FhirContext ctx = FhirContext.forDstu2Hl7Org();
IGenericClient client = newClient(ctx);
IGenericClient client = newClient(ctx,"");
int total;
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
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);
IResource res = nextEntry.getResource();
IdDt nextResourceId = null;
@ -313,7 +318,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
case POST: {
// CREATE
@SuppressWarnings("rawtypes")
IFhirResourceDao resourceDao = getDao(res.getClass());
IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass());
res.setId((String) null);
DaoMethodOutcome outcome;
Entry newEntry = response.addEntry();
@ -338,7 +343,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
case PUT: {
// UPDATE
@SuppressWarnings("rawtypes")
IFhirResourceDao resourceDao = getDao(res.getClass());
IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass());
DaoMethodOutcome outcome;
Entry newEntry = response.addEntry();
@ -490,6 +495,14 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
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,
Entry newEntry, String theResourceType, IResource theRes) {
IdDt newId = (IdDt) outcome.getId().toUnqualifiedVersionless();

View File

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

View File

@ -474,6 +474,17 @@ public class JsonParserDstu2Test {
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
public void testParseAndEncodeBundle() throws Exception {
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.OperationOutcome.IssueSeverity;
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;
@ -68,8 +70,8 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
}
@Override
public org.hl7.fhir.instance.utils.IWorkerContext.ValidationResult validateCode(String theCodeSystem, String theCode, String theDisplay) {
return new org.hl7.fhir.instance.utils.IWorkerContext.ValidationResult(IssueSeverity.INFORMATION, "Unknown code: " + theCodeSystem + " / " + theCode);
public CodeValidationResult validateCode(String theCodeSystem, String theCode, String theDisplay) {
return new CodeValidationResult(IssueSeverity.INFORMATION, "Unknown code: " + theCodeSystem + " / " + theCode);
}
@Override
@ -77,4 +79,9 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
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.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.validation.IValidationSupport.CodeValidationResult;
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 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) {
myCtx = theCtx;
}
@Override
public ValueSetExpansionComponent expandVS(ConceptSetComponent theInc) {
throw new UnsupportedOperationException();
return myValidationSupport.expandValueSet(theInc);
}
@Override
@ -354,7 +341,11 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
@Override
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

View File

@ -25,121 +25,148 @@ import ca.uhn.fhir.util.ResourceReferenceInfo;
public class FhirQuestionnaireResponseValidator extends BaseValidatorBridge {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirQuestionnaireResponseValidator.class);
private IResourceLoader myResourceLoader;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory
.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)
* <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.
*
* @param 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;
}
/**
* 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)
* <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.
*
* @param 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
protected List<ValidationMessage> validate(IValidationContext<?> theCtx) {
Object resource = theCtx.getResource();
if (!(theCtx.getResource() instanceof IBaseResource)) {
ourLog.debug("Not validating object of type {}", theCtx.getResource().getClass());
return Collections.emptyList();
}
@Override
protected List<ValidationMessage> validate(IValidationContext<?> theCtx) {
Object resource = theCtx.getResource();
if (!(theCtx.getResource() instanceof IBaseResource)) {
ourLog.debug("Not validating object of type {}", theCtx.getResource().getClass());
return Collections.emptyList();
}
if (resource instanceof QuestionnaireResponse) {
return doValidate(theCtx, (QuestionnaireResponse) resource);
}
if (resource instanceof QuestionnaireResponse) {
return doValidate(theCtx, (QuestionnaireResponse) resource);
}
RuntimeResourceDefinition def = theCtx.getFhirContext().getResourceDefinition((IBaseResource) resource);
if ("QuestionnaireResponse".equals(def.getName()) == false) {
return Collections.emptyList();
}
RuntimeResourceDefinition def = theCtx.getFhirContext().getResourceDefinition((IBaseResource) resource);
if ("QuestionnaireResponse".equals(def.getName()) == false) {
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();
String string = p.encodeResourceToString((IBaseResource) resource);
QuestionnaireResponse qa = p.parseResource(QuestionnaireResponse.class, string);
IParser p = theCtx.getFhirContext().newJsonParser();
String string = p.encodeResourceToString((IBaseResource) resource);
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();
ArrayList<ValidationMessage> retVal = new ArrayList<ValidationMessage>();
WorkerContext workerCtx = new WorkerContext();
ArrayList<ValidationMessage> retVal = new ArrayList<ValidationMessage>();
if (!loadReferences(theResource, workerCtx, theValCtx, retVal)) {
return retVal;
}
if (!loadReferences(theResource, workerCtx, theValCtx, retVal)) {
return retVal;
}
QuestionnaireResponseValidator val = new QuestionnaireResponseValidator(workerCtx);
QuestionnaireResponseValidator val = new QuestionnaireResponseValidator(workerCtx);
val.validate(retVal, theResource);
return retVal;
}
val.validate(retVal, theResource);
return retVal;
}
private boolean loadReferences(IBaseResource theResource, WorkerContext theWorkerCtx, IValidationContext<?> theValCtx, ArrayList<ValidationMessage> theMessages) {
List<ResourceReferenceInfo> refs = theValCtx.getFhirContext().newTerser().getAllResourceReferences(theResource);
private boolean loadReferences(IBaseResource theResource, WorkerContext theWorkerCtx, IValidationContext<?> theValCtx,
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) {
IIdType nextRef = nextRefInfo.getResourceReference().getReferenceElement();
String resourceType = nextRef.getResourceType();
if (isBlank(resourceType)) {
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));
} else if ("ValueSet".equals(resourceType)) {
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 (ResourceReferenceInfo nextRefInfo : refs) {
IIdType nextRef = nextRefInfo.getResourceReference().getReferenceElement();
String resourceType = nextRef.getResourceType();
if (nextRef.isLocal()) {
IBaseResource resource = nextRefInfo.getResourceReference().getResource();
if (resource instanceof ValueSet) {
theWorkerCtx.getValueSets().put(nextRef.getValue(), (ValueSet) resource);
newResources.add(resource);
} else if (resource instanceof Questionnaire) {
theWorkerCtx.getQuestionnaires().put(nextRef.getValue(), (Questionnaire) resource);
newResources.add(resource);
} else if (resource == null) {
theMessages.add(new ValidationMessage(Source.QuestionnaireResponseValidator,
org.hl7.fhir.instance.model.OperationOutcome.IssueType.INVALID,
"Invalid reference '" + nextRef.getValue() + "' - No contained resource with this ID found", IssueSeverity.FATAL));
}
} else if (isBlank(resourceType)) {
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));
} else if ("ValueSet".equals(resourceType)) {
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) {
boolean outcome = loadReferences(nextAddedResource, theWorkerCtx, theValCtx, theMessages);
if (!outcome) {
return false;
}
}
for (IBaseResource nextAddedResource : newResources) {
boolean outcome = loadReferences(nextAddedResource, theWorkerCtx, theValCtx, theMessages);
if (!outcome) {
return false;
}
}
return true;
}
return true;
}
private <T extends IBaseResource> T tryToLoad(Class<T> theType, IIdType theReference, List<ValidationMessage> theMessages) {
if (myResourceLoader == null) {
theMessages.add(new ValidationMessage().setLevel(IssueSeverity.FATAL).setMessage("No resource loader present, could not load " + theReference));
return null;
}
private <T extends IBaseResource> T tryToLoad(Class<T> theType, IIdType theReference,
List<ValidationMessage> theMessages) {
if (myResourceLoader == null) {
theMessages.add(new ValidationMessage().setLevel(IssueSeverity.FATAL)
.setMessage("No resource loader present, could not load " + theReference));
return null;
}
try {
T retVal = myResourceLoader.load(theType, theReference);
if (retVal == null) {
throw new IllegalStateException("ResourceLoader returned null. This is a bug with the resourceloader. Reference was: " + theReference);
}
return retVal;
} catch (ResourceNotFoundException e) {
theMessages.add(new ValidationMessage().setLevel(IssueSeverity.FATAL).setMessage("Reference could not be found: " + theReference));
return null;
}
}
try {
T retVal = myResourceLoader.load(theType, theReference);
if (retVal == null) {
throw new IllegalStateException(
"ResourceLoader returned null. This is a bug with the resourceloader. Reference was: " + theReference);
}
return retVal;
} catch (ResourceNotFoundException e) {
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;
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 ca.uhn.fhir.context.FhirContext;
@ -8,15 +12,22 @@ import ca.uhn.fhir.context.FhirContext;
public interface IValidationSupport {
/**
* Returns <code>true</code> if codes in the given code system can be
* validated
* Expands the given portion of a ValueSet
*
* @param theInclude
* The portion to include
* @return The expansion
*/
ValueSetExpansionComponent expandValueSet(ConceptSetComponent theInclude);
/**
* Fetch a code system by ID
*
* @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
* The code system
* @return The valueset (must not be null, but can be an empty ValueSet)
*/
boolean isCodeSystemSupported(String theSystem);
ValueSet fetchCodeSystem(String theSystem);
/**
* 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);
/**
* 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
* 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
* @return Returns a validation result object
*/
org.hl7.fhir.instance.utils.IWorkerContext.ValidationResult validateCode(String theCodeSystem, String theCode,
String theDisplay);
CodeValidationResult validateCode(String theCodeSystem, String theCode, String theDisplay);
/**
* Fetch a code system by ID
*
* @param theSystem
* The code system
* @return The valueset (must not be null, but can be an empty ValueSet)
*/
ValueSet fetchCodeSystem(String theSystem);
public class CodeValidationResult {
private ConceptDefinitionComponent definition;
private String message;
private IssueSeverity severity;
public CodeValidationResult(ConceptDefinitionComponent definition) {
this.definition = definition;
}
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;
public class ValueSetExpanderSimple implements ValueSetExpander {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValueSetExpanderSimple.class);
private IWorkerContext context;
private List<ValueSetExpansionContainsComponent> codes = new ArrayList<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);
} 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
// that might fail too, but it might not, later.
return new ValueSetExpansionOutcome(new ValueSetCheckerSimple(source, factory, context), e.getMessage());

View File

@ -1,5 +1,7 @@
package org.hl7.fhir.instance.validation;
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.util.ArrayList;
import java.util.List;
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");
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) {
ElementDefinitionBindingComponent binding = context.getBinding();
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
for (ElementInfo ei : children) {
if (ei.definition != null) {
String type = null;
String type = determineType(ei.definition, ei.name, ei.line(), ei.col(), errors, stack, profile);
ElementDefinition typeDefn = null;
if (ei.definition.getType().size() == 1 && !ei.definition.getType().get(0).getCode().equals("*") && !ei.definition.getType().get(0).getCode().equals("Element")
&& !ei.definition.getType().get(0).getCode().equals("BackboneElement"))
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) {
if (isBlank(type) && ei.definition.getNameReference() != null) {
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));
String localStackLiterapPath = localStack.getLiteralPath();
String eiPath = ei.path;
@ -1793,21 +1763,36 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (isPrimitiveType(type))
checkPrimitive(errors, ei.path, type, ei.definition, ei.element);
else {
if (type.equals("Identifier"))
if (type.equals("Identifier")) {
checkIdentifier(errors, ei.path, ei.element, ei.definition);
else if (type.equals("Coding"))
checkCoding(errors, ei.path, ei.element, profile, ei.definition);
else if (type.equals("CodeableConcept"))
} else if (type.equals("Coding")) {
/*
* 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);
else if (type.equals("Reference"))
} else if (type.equals("Reference")) {
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);
else if (type.equals("Resource"))
} else if (type.equals("Resource")) {
validateContains(errors, ei.path, ei.definition, definition, ei.element, localStack, !isBundleEntry(ei.path)); // if
// (str.matches(".*([.,/])work\\1$"))
else {
} else {
StructureDefinition p = getProfileForType(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);
@ -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) {
// TODO Auto-generated method stub
@ -2378,6 +2407,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return logicalPaths == null ? new ArrayList<String>() : logicalPaths;
}
public NodeStack getParent() {
return this.parent;
}
private ElementDefinition getType() {
return type;
}

View File

@ -9,7 +9,9 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.IOUtils;
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.ValueSet;
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.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.validation.IValidationSupport.CodeValidationResult;
public class FhirInstanceValidatorTest {
@ -37,6 +41,7 @@ public class FhirInstanceValidatorTest {
private FhirValidator myVal;
private ArrayList<String> myValidConcepts;
private Map<String, ValueSetExpansionComponent> mySupportedCodeSystemsForExpansion;
private void addValidConcept(String theSystem, String theCode) {
myValidConcepts.add(theSystem + "___" + theCode);
@ -52,39 +57,57 @@ public class FhirInstanceValidatorTest {
myInstanceVal = new FhirInstanceValidator();
myVal.registerValidatorModule(myInstanceVal);
mySupportedCodeSystemsForExpansion = new HashMap<String, ValueSet.ValueSetExpansionComponent>();
myValidConcepts = new ArrayList<String>();
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>() {
@Override
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 });
return retVal;
}
});
when(myMockSupport.fetchResource(any(FhirContext.class), any(Class.class), any(String.class))).thenAnswer(new Answer<IBaseResource>() {
@Override
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>() {
when(myMockSupport.fetchResource(any(FhirContext.class), any(Class.class), any(String.class)))
.thenAnswer(new Answer<IBaseResource>() {
@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 code = (String) theInvocation.getArguments()[1];
org.hl7.fhir.instance.utils.IWorkerContext.ValidationResult retVal;
CodeValidationResult retVal;
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 {
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;
}
});
@ -104,7 +127,8 @@ public class FhirInstanceValidatorTest {
int index = 0;
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++;
if (next.getSeverity() != ResultSeverityEnum.INFORMATION) {
@ -115,6 +139,21 @@ public class FhirInstanceValidatorTest {
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
public void testValidateRawJsonResource() {
// @formatter:off
@ -152,7 +191,8 @@ public class FhirInstanceValidatorTest {
@Test
public void testValidateRawXmlResourceBadAttributes() {
// @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
ValidationResult output = myVal.validateWithResult(input);
@ -173,7 +213,8 @@ public class FhirInstanceValidatorTest {
ValidationResult output = myVal.validateWithResult(input);
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
*/
@Test
@Ignore
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);
List<SingleValidationMessage> res = logResultsAndReturnNonInformationalOnes(output);
@ -205,7 +246,8 @@ public class FhirInstanceValidatorTest {
ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
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
@ -224,9 +266,11 @@ public class FhirInstanceValidatorTest {
ValidationResult output = myVal.validateWithResult(input);
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 '/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(""));
}
@ -245,8 +289,8 @@ public class FhirInstanceValidatorTest {
@Test
public void testValidateResourceWithDefaultValuesetBadCode() {
String input = "<Observation xmlns=\"http://hl7.org/fhir\">\n" + " <status value=\"notvalidcode\"/>\n" + " <code>\n"
+ " <text value=\"No code here!\"/>\n" + " </code>\n" + "</Observation>";
String input = "<Observation xmlns=\"http://hl7.org/fhir\">\n" + " <status value=\"notvalidcode\"/>\n"
+ " <code>\n" + " <text value=\"No code here!\"/>\n" + " </code>\n" + "</Observation>";
ValidationResult output = myVal.validateWithResult(input);
assertEquals(
"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
public void testValidateResourceWithExampleBindingCodeValidationPassing() {
public void testValidateResourceWithExampleBindingCodeValidationPassingLoinc() {
Observation input = new Observation();
myInstanceVal.setValidationSupport(myMockSupport);
@ -283,4 +327,39 @@ public class FhirInstanceValidatorTest {
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
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"));
}
@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
*/

58
pom.xml
View File

@ -212,8 +212,9 @@
<apache_httpclient_version>4.4</apache_httpclient_version>
<apache_httpcore_version>4.4</apache_httpcore_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
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> -->
<!-- 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 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_validator_version>5.1.0.Final</hibernate_validator_version>
<jetty_version>9.2.6.v20141205</jetty_version>
@ -429,6 +430,11 @@
<artifactId>jetty-webapp</artifactId>
<version>9.2.6.v20141205</version>
</dependency>
<dependency>
<groupId>org.fusesource.jansi</groupId>
<artifactId>jansi</artifactId>
<version>1.11</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
@ -833,43 +839,43 @@
<configuration>
<target>
<copy todir="target/site/apidocs">
<fileset dir="hapi-fhir-base/target/site/apidocs"/>
<fileset dir="hapi-fhir-base/target/site/apidocs" />
</copy>
<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 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 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 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 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 todir="target/site/xref-base">
<fileset dir="hapi-fhir-base/target/site/xref"/>
<fileset dir="hapi-fhir-base/target/site/xref" />
</copy>
<!-- <copy todir="target/site/cobertura"> <fileset dir="hapi-fhir-cobertura/target/site/cobertura" /> </copy> -->
<copy todir="target/site">
<fileset dir="hapi-fhir-base/target/site" includes="checkstyle.*"/>
<fileset dir="hapi-fhir-base/target/site" includes="checkstyle.*" />
</copy>
<echo>Fixing Checkstyle Report</echo>
<replace dir="target/site" summary="true">
<include name="checkstyle.html"/>
<include name="checkstyle.html" />
<replacetoken>"../../</replacetoken>
<replacevalue>"./</replacevalue>
</replace>
<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>
<replacevalue>./css/bootstrap-responsive.min.css</replacevalue>
</replace>
<replace dir="target/site" summary="true">
<include name="index.html"/>
<include name="index.html" />
<replacetoken><![CDATA[<h2 id="Welcome">Welcome</h2>]]></replacetoken>
<replacevalue><![CDATA[<div class="jumbotron subhead">
<div class="row" id="banner">
@ -898,33 +904,33 @@
<target>
<echo>Adding Fontawesome</echo>
<replace dir="target/site" summary="true">
<include name="*.html"/>
<include name="*.html" />
<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>
</replace>
<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>
<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 dir="target/site" summary="true">
<include name="*.html"/>
<include name="*.html" />
<replacetoken><![CDATA[data-toggle="dropdown">Test Servers <]]></replacetoken>
<replacevalue><![CDATA[data-toggle="dropdown"><i class="fa fa-fire"></i>&nbsp;Test Servers&nbsp;<]]></replacevalue>
</replace>
<replace dir="target/site" summary="true">
<include name="*.html"/>
<include name="*.html" />
<replacetoken><![CDATA[data-toggle="dropdown">Documentation <]]></replacetoken>
<replacevalue><![CDATA[data-toggle="dropdown"><i class="fa fa-book"></i>&nbsp;Documentation&nbsp;<]]></replacevalue>
</replace>
<replace dir="target/site" summary="true">
<include name="*.html"/>
<include name="*.html" />
<replacetoken><![CDATA[data-toggle="dropdown">Get Help <]]></replacetoken>
<replacevalue><![CDATA[data-toggle="dropdown"><i class="fa fa-support"></i>&nbsp;Get Help&nbsp;<]]></replacevalue>
</replace>
<echo>Changing Breadcrumbs</echo>
<replace dir="target/site" summary="true">
<include name="doc_*.html"/>
<include name="doc_*.html" />
<replacetoken><![CDATA[<li class="divider">/</li>]]></replacetoken>
<replacevalue><![CDATA[<li class="divider">/</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>
<replace dir="target/site" summary="true">
<include name="**/*.html"></include>
<replacefilter token="#build#" value="${label}"/>
<replacefilter token="#version#" value="${project.version}"/>
<replacefilter token="#build#" value="${label}" />
<replacefilter token="#version#" value="${project.version}" />
<replacetoken><![CDATA[</body>]]></replacetoken>
<replacevalue><![CDATA[
<script>
@ -1114,8 +1120,9 @@
<reporting>
<plugins>
<!-- <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-checkstyle-plugin</artifactId> <reportSets> <reportSet> <reports><report>checkstyle-aggregate</report></reports> </reportSet>
</reportSets> <configuration> <configLocation>config/sun_checks.xml</configLocation> <includes> hapi-fhir-base/src/main/java/**/*.java </includes> </configuration> </plugin> -->
<!-- <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-checkstyle-plugin</artifactId> <reportSets> <reportSet> <reports><report>checkstyle-aggregate</report></reports>
</reportSet> </reportSets> <configuration> <configLocation>config/sun_checks.xml</configLocation> <includes> hapi-fhir-base/src/main/java/**/*.java </includes> </configuration>
</plugin> -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-changes-plugin</artifactId>
@ -1190,8 +1197,9 @@
</modules>
<build>
<plugins>
<!-- <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>${maven_assembly_plugin_version}</version> <executions> <execution> <phase>package</phase> <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> -->
<!-- <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>${maven_assembly_plugin_version}</version> <executions> <execution> <phase>package</phase>
<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>
</build>

View File

@ -181,6 +181,12 @@
definitions provided either by HL7 or by the user.
</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">
<p>