Merge remote-tracking branch 'origin/master' into call_access_method

This commit is contained in:
patrick-werner 2018-05-17 11:29:24 +02:00
commit dcdbe51e7e
23 changed files with 91910 additions and 366 deletions

View File

@ -109,15 +109,15 @@ public class SingleValidationMessage {
public String toString() { public String toString() {
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
if (myLocationCol != null || myLocationLine != null) { if (myLocationCol != null || myLocationLine != null) {
b.append("myLocationCol", myLocationCol); b.append("col", myLocationCol);
b.append("myLocationRow", myLocationLine); b.append("row", myLocationLine);
} }
if (myLocationString != null) { if (myLocationString != null) {
b.append("myLocationString", myLocationString); b.append("locationString", myLocationString);
} }
b.append("myMessage", myMessage); b.append("message", myMessage);
if (mySeverity != null) { if (mySeverity != null) {
b.append("mySeverity", mySeverity.getCode()); b.append("severity", mySeverity.getCode());
} }
return b.toString(); return b.toString();
} }

View File

@ -33,6 +33,7 @@ import org.fusesource.jansi.AnsiConsole;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.lang.management.ManagementFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -52,16 +53,20 @@ public abstract class BaseApp {
loggingConfigOff(); loggingConfigOff();
// We don't use qualified names for loggers in CLI // We don't use qualified names for loggers in CLI
ourLog = LoggerFactory.getLogger(App.class.getSimpleName()); ourLog = LoggerFactory.getLogger(App.class);
} }
private MyShutdownHook myShutdownHook;
private boolean myShutdownHookHasNotRun;
private void logAppHeader() { private void logAppHeader() {
System.out.flush(); System.out.flush();
System.out.println("------------------------------------------------------------"); System.out.println("------------------------------------------------------------");
System.out.println("\ud83d\udd25 " + ansi().bold() + " " + provideProductName() + ansi().boldOff() + " " + provideProductVersion() + " - Command Line Tool"); System.out.println("\ud83d\udd25 " + ansi().bold() + " " + provideProductName() + ansi().boldOff() + " " + provideProductVersion() + " - Command Line Tool");
System.out.println("------------------------------------------------------------"); System.out.println("------------------------------------------------------------");
System.out.println("Max configured JVM memory (Xmx): " + FileHelper.getFileSizeDisplay(Runtime.getRuntime().maxMemory(), 1)); System.out.println("Process ID : " + ManagementFactory.getRuntimeMXBean().getName());
System.out.println("Detected Java version: " + System.getProperty("java.version")); System.out.println("Max configured JVM memory (Xmx) : " + FileHelper.getFileSizeDisplay(Runtime.getRuntime().maxMemory(), 1));
System.out.println("Detected Java version : " + System.getProperty("java.version"));
System.out.println("------------------------------------------------------------"); System.out.println("------------------------------------------------------------");
} }
@ -197,6 +202,9 @@ public abstract class BaseApp {
return; return;
} }
myShutdownHook = new MyShutdownHook(command);
Runtime.getRuntime().addShutdownHook(myShutdownHook);
Options options = command.getOptions(); Options options = command.getOptions();
DefaultParser parser = new DefaultParser(); DefaultParser parser = new DefaultParser();
CommandLine parsedOptions; CommandLine parsedOptions;
@ -215,6 +223,9 @@ public abstract class BaseApp {
// Actually execute the command // Actually execute the command
command.run(parsedOptions); command.run(parsedOptions);
myShutdownHookHasNotRun = true;
runCleanupHookAndUnregister();
if (!"true".equals(System.getProperty("test"))) { if (!"true".equals(System.getProperty("test"))) {
System.exit(0); System.exit(0);
} }
@ -225,9 +236,11 @@ public abstract class BaseApp {
System.err.println(" " + ansi().fg(Ansi.Color.RED).bold() + e.getMessage()); System.err.println(" " + ansi().fg(Ansi.Color.RED).bold() + e.getMessage());
System.err.println("" + ansi().fg(Ansi.Color.WHITE).boldOff()); System.err.println("" + ansi().fg(Ansi.Color.WHITE).boldOff());
logCommandUsageNoHeader(command); logCommandUsageNoHeader(command);
runCleanupHookAndUnregister();
System.exit(1); System.exit(1);
} catch (CommandFailureException e) { } catch (CommandFailureException e) {
ourLog.error(e.getMessage()); ourLog.error(e.getMessage());
runCleanupHookAndUnregister();
if ("true".equals(System.getProperty("test"))) { if ("true".equals(System.getProperty("test"))) {
throw e; throw e;
} else { } else {
@ -235,12 +248,22 @@ public abstract class BaseApp {
} }
} catch (Throwable t) { } catch (Throwable t) {
ourLog.error("Error during execution: ", t); ourLog.error("Error during execution: ", t);
runCleanupHookAndUnregister();
if ("true".equals(System.getProperty("test"))) { if ("true".equals(System.getProperty("test"))) {
throw new CommandFailureException("Error: " + t.toString(), t); throw new CommandFailureException("Error: " + t.toString(), t);
} else { } else {
System.exit(1); System.exit(1);
} }
} }
}
private void runCleanupHookAndUnregister() {
if (myShutdownHookHasNotRun) {
Runtime.getRuntime().removeShutdownHook(myShutdownHook);
myShutdownHook.run();
myShutdownHookHasNotRun = false;
}
} }
private void validateJavaVersion() { private void validateJavaVersion() {
@ -275,4 +298,17 @@ public abstract class BaseApp {
} }
private class MyShutdownHook extends Thread {
private final BaseCommand myFinalCommand;
public MyShutdownHook(BaseCommand theFinalCommand) {
myFinalCommand = theFinalCommand;
}
@Override
public void run() {
ourLog.info(provideProductName() + " is shutting down...");
myFinalCommand.cleanup();
}
}
} }

View File

@ -152,6 +152,13 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
addOptionalOption(theOptions, VERBOSE_LOGGING_PARAM, VERBOSE_LOGGING_PARAM_LONGOPT, false, VERBOSE_LOGGING_PARAM_DESC); addOptionalOption(theOptions, VERBOSE_LOGGING_PARAM, VERBOSE_LOGGING_PARAM_LONGOPT, false, VERBOSE_LOGGING_PARAM_DESC);
} }
/**
* Subclasses may override if they want, to do any cleanup they need to do.
*/
public void cleanup() {
// nothing
}
@Override @Override
public int compareTo(BaseCommand theO) { public int compareTo(BaseCommand theO) {
return getCommandName().compareTo(theO.getCommandName()); return getCommandName().compareTo(theO.getCommandName());

View File

@ -29,6 +29,7 @@ import org.apache.commons.cli.CommandLine;
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.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebAppContext;
import org.springframework.web.context.ContextLoader; import org.springframework.web.context.ContextLoader;
@ -193,9 +194,22 @@ public class RunServerCommand extends BaseCommand {
ourLog.info("Web Testing UI : http://localhost:{}/", myPort); ourLog.info("Web Testing UI : http://localhost:{}/", myPort);
ourLog.info("Server Base URL: http://localhost:{}{}", myPort, path); ourLog.info("Server Base URL: http://localhost:{}{}", myPort, path);
// Never quit.. We'll let the user ctrl-C their way out.
loopForever();
} }
@SuppressWarnings("InfiniteLoopStatement")
private void loopForever() {
while (true) {
try {
Thread.sleep(DateUtils.MILLIS_PER_MINUTE);
} catch (InterruptedException theE) {
// ignore
}
}
}
public static void main(String[] theArgs) { public static void main(String[] theArgs) {

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.igpacks.parser.IgPackParserDstu2; import ca.uhn.fhir.igpacks.parser.IgPackParserDstu2;
import ca.uhn.fhir.igpacks.parser.IgPackParserDstu3; import ca.uhn.fhir.igpacks.parser.IgPackParserDstu3;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.LenientErrorHandler; import ca.uhn.fhir.parser.LenientErrorHandler;
import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.SingleValidationMessage; import ca.uhn.fhir.validation.SingleValidationMessage;
@ -103,7 +104,7 @@ public class ValidateCommand extends BaseCommand {
parseFhirContext(theCommandLine); parseFhirContext(theCommandLine);
String fileName = theCommandLine.getOptionValue("n"); String fileName = theCommandLine.getOptionValue("n");
String contents = theCommandLine.getOptionValue("c"); String contents = theCommandLine.getOptionValue("d");
if (isNotBlank(fileName) && isNotBlank(contents)) { if (isNotBlank(fileName) && isNotBlank(contents)) {
throw new ParseException("Can not supply both a file (-n) and data (-d)"); throw new ParseException("Can not supply both a file (-n) and data (-d)");
} }
@ -199,7 +200,12 @@ public class ValidateCommand extends BaseCommand {
val.setValidateAgainstStandardSchema(theCommandLine.hasOption("x")); val.setValidateAgainstStandardSchema(theCommandLine.hasOption("x"));
val.setValidateAgainstStandardSchematron(theCommandLine.hasOption("s")); val.setValidateAgainstStandardSchematron(theCommandLine.hasOption("s"));
ValidationResult results = val.validateWithResult(contents); ValidationResult results;
try {
results = val.validateWithResult(contents);
} catch (DataFormatException e) {
throw new CommandFailureException(e.getMessage());
}
StringBuilder b = new StringBuilder("Validation results:" + ansi().boldOff()); StringBuilder b = new StringBuilder("Validation results:" + ansi().boldOff());
int count = 0; int count = 0;

View File

@ -53,7 +53,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class ValidationDataUploader extends BaseCommand { public class ValidationDataUploader extends BaseCommand {
// TODO: Don't use qualified names for loggers in HAPI CLI.
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidationDataUploader.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidationDataUploader.class);
private ArrayList<IIdType> myExcludes = new ArrayList<>(); private ArrayList<IIdType> myExcludes = new ArrayList<>();
@ -354,9 +353,10 @@ public class ValidationDataUploader extends BaseCommand {
ourLog.info("Finished uploading ValueSets"); ourLog.info("Finished uploading ValueSets");
uploadDstu3Profiles(theCtx, client, "profiles-resources"); uploadDstu3Profiles(theCtx, client, "profile/profiles-resources");
uploadDstu3Profiles(theCtx, client, "profiles-types"); uploadDstu3Profiles(theCtx, client, "profile/profiles-types");
uploadDstu3Profiles(theCtx, client, "profiles-others"); uploadDstu3Profiles(theCtx, client, "profile/profiles-others");
uploadDstu3Profiles(theCtx, client, "extension/extension-definitions");
ourLog.info("Finished uploading ValueSets"); ourLog.info("Finished uploading ValueSets");
@ -446,9 +446,10 @@ public class ValidationDataUploader extends BaseCommand {
ourLog.info("Finished uploading ValueSets"); ourLog.info("Finished uploading ValueSets");
uploadR4Profiles(theCtx, client, "profiles-resources"); uploadR4Profiles(theCtx, client, "profile/profiles-resources");
uploadR4Profiles(theCtx, client, "profiles-types"); uploadR4Profiles(theCtx, client, "profile/profiles-types");
uploadR4Profiles(theCtx, client, "profiles-others"); uploadR4Profiles(theCtx, client, "profile/profiles-others");
uploadR4Profiles(theCtx, client, "extension/extension-definitions");
ourLog.info("Finished uploading ValueSets"); ourLog.info("Finished uploading ValueSets");
@ -457,14 +458,14 @@ public class ValidationDataUploader extends BaseCommand {
ourLog.info("Finished uploading definitions to server (took {} ms)", delay); ourLog.info("Finished uploading definitions to server (took {} ms)", delay);
} }
private void uploadDstu3Profiles(FhirContext ctx, IGenericClient client, String name) throws CommandFailureException { private void uploadDstu3Profiles(FhirContext ctx, IGenericClient client, String theName) throws CommandFailureException {
int total; int total;
int count; int count;
org.hl7.fhir.dstu3.model.Bundle bundle; org.hl7.fhir.dstu3.model.Bundle bundle;
ourLog.info("Uploading " + name); ourLog.info("Uploading " + theName);
String vsContents; String vsContents;
try { try {
vsContents = IOUtils.toString(ValidationDataUploader.class.getResourceAsStream("/org/hl7/fhir/dstu3/model/profile/" + name + ".xml"), "UTF-8"); vsContents = IOUtils.toString(ValidationDataUploader.class.getResourceAsStream("/org/hl7/fhir/dstu3/model/" + theName + ".xml"), "UTF-8");
} catch (IOException e) { } catch (IOException e) {
throw new CommandFailureException(e.toString()); throw new CommandFailureException(e.toString());
} }
@ -498,26 +499,26 @@ public class ValidationDataUploader extends BaseCommand {
continue; continue;
} }
ourLog.info("Uploading {} StructureDefinition {}/{} : {}", new Object[] {name, count, total, next.getIdElement().getValue()}); ourLog.info("Uploading {} StructureDefinition {}/{} : {}", new Object[] {theName, count, total, next.getIdElement().getValue()});
client.update().resource(next).execute(); client.update().resource(next).execute();
count++; count++;
} }
} }
private void uploadR4Profiles(FhirContext ctx, IGenericClient client, String name) throws CommandFailureException { private void uploadR4Profiles(FhirContext theContext, IGenericClient theClient, String theName) throws CommandFailureException {
int total; int total;
int count; int count;
org.hl7.fhir.r4.model.Bundle bundle; org.hl7.fhir.r4.model.Bundle bundle;
ourLog.info("Uploading " + name); ourLog.info("Uploading " + theName);
String vsContents; String vsContents;
try { try {
vsContents = IOUtils.toString(ValidationDataUploader.class.getResourceAsStream("/org/hl7/fhir/r4/model/profile/" + name + ".xml"), "UTF-8"); vsContents = IOUtils.toString(ValidationDataUploader.class.getResourceAsStream("/org/hl7/fhir/r4/model/" + theName + ".xml"), "UTF-8");
} catch (IOException e) { } catch (IOException e) {
throw new CommandFailureException(e.toString()); throw new CommandFailureException(e.toString());
} }
bundle = ctx.newXmlParser().parseResource(org.hl7.fhir.r4.model.Bundle.class, vsContents); bundle = theContext.newXmlParser().parseResource(org.hl7.fhir.r4.model.Bundle.class, vsContents);
filterBundle(bundle); filterBundle(bundle);
total = bundle.getEntry().size(); total = bundle.getEntry().size();
count = 1; count = 1;
@ -546,9 +547,9 @@ public class ValidationDataUploader extends BaseCommand {
continue; continue;
} }
ourLog.info("Uploading {} StructureDefinition {}/{} : {}", new Object[] {name, count, total, next.getIdElement().getValue()}); ourLog.info("Uploading {} StructureDefinition {}/{} : {}", new Object[] {theName, count, total, next.getIdElement().getValue()});
try { try {
client.update().resource(next).execute(); theClient.update().resource(next).execute();
} catch (BaseServerResponseException e) { } catch (BaseServerResponseException e) {
ourLog.warn("Server responded HTTP " + e.getStatusCode() + ": " + e.toString()); ourLog.warn("Server responded HTTP " + e.getStatusCode() + ": " + e.toString());
} }

View File

@ -511,6 +511,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected Set<String> extractResourceLinks(ResourceTable theEntity, IBaseResource theResource, Set<ResourceLink> theLinks, Date theUpdateTime) { protected Set<String> extractResourceLinks(ResourceTable theEntity, IBaseResource theResource, Set<ResourceLink> theLinks, Date theUpdateTime) {
HashSet<String> retVal = new HashSet<>(); HashSet<String> retVal = new HashSet<>();
String resourceType = theEntity.getResourceType();
/* /*
* For now we don't try to load any of the links in a bundle if it's the actual bundle we're storing.. * For now we don't try to load any of the links in a bundle if it's the actual bundle we're storing..
@ -580,6 +581,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
} }
} else if (myContext.getElementDefinition((Class<? extends IBase>) nextObject.getClass()).getName().equals("uri")) { } else if (myContext.getElementDefinition((Class<? extends IBase>) nextObject.getClass()).getName().equals("uri")) {
continue; continue;
} else if (resourceType.equals("Consent") && nextPathAndRef.getPath().equals("Consent.source")) {
// Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that
continue;
} else { } else {
if (!multiType) { if (!multiType) {
if (nextSpDef.getName().equals("sourceuri")) { if (nextSpDef.getName().equals("sourceuri")) {

View File

@ -105,7 +105,8 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
*/ */
@Override @Override
public Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) { public Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamDate> retVal = new HashSet<ResourceIndexedSearchParamDate>(); HashSet<ResourceIndexedSearchParamDate> retVal = new HashSet<>();
String resourceType = theEntity.getResourceType();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource); Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) { for (RuntimeSearchParam nextSpDef : searchParams) {
@ -164,6 +165,9 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
} else if (nextObject instanceof StringType) { } else if (nextObject instanceof StringType) {
// CarePlan.activitydate can be a string // CarePlan.activitydate can be a string
continue; continue;
} else if (resourceType.equals("Consent") && nextPath.equals("Consent.source")) {
// Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that
continue;
} else { } else {
if (!multiType) { if (!multiType) {
throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass()); throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass());

View File

@ -20,12 +20,28 @@ package ca.uhn.fhir.jpa.dao.r4;
* #L% * #L%
*/ */
import static org.apache.commons.lang3.StringUtils.isNotBlank; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import java.util.*; import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.util.DeleteConflict;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.IValidationContext;
import ca.uhn.fhir.validation.IValidatorModule;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.OperationOutcome; import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity; import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity;
@ -33,20 +49,10 @@ import org.hl7.fhir.r4.model.OperationOutcome.OperationOutcomeIssueComponent;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import java.util.ArrayList;
import ca.uhn.fhir.context.RuntimeSearchParam; import java.util.List;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import static org.apache.commons.lang3.StringUtils.isNotBlank;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.util.DeleteConflict;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.validation.*;
public class FhirResourceDaoR4<T extends IAnyResource> extends BaseHapiFhirResourceDao<T> { public class FhirResourceDaoR4<T extends IAnyResource> extends BaseHapiFhirResourceDao<T> {

View File

@ -468,6 +468,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
continue; continue;
} }
String resourceType = theEntity.getResourceType();
String nextPath = nextSpDef.getPath(); String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) { if (isBlank(nextPath)) {
continue; continue;
@ -478,8 +479,8 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
multiType = true; multiType = true;
} }
List<String> systems = new ArrayList<String>(); List<String> systems = new ArrayList<>();
List<String> codes = new ArrayList<String>(); List<String> codes = new ArrayList<>();
for (Object nextObject : extractValues(nextPath, theResource)) { for (Object nextObject : extractValues(nextPath, theResource)) {
@ -555,9 +556,15 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
} else if (nextObject instanceof LocationPositionComponent) { } else if (nextObject instanceof LocationPositionComponent) {
ourLog.warn("Position search not currently supported, not indexing location"); ourLog.warn("Position search not currently supported, not indexing location");
continue; continue;
} else if (nextObject instanceof StructureDefinition.StructureDefinitionContextComponent) {
ourLog.warn("StructureDefinition context indexing not currently supported"); // TODO: implement this
continue;
} else if (resourceType.equals("Consent") && nextPath.equals("Consent.source")) {
// Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that
continue;
} else { } else {
if (!multiType) { if (!multiType) {
throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass()); throw new ConfigurationException("Search param " + nextSpDef.getName() + " with path " + nextPath + " is of unexpected datatype: " + nextObject.getClass());
} else { } else {
continue; continue;
} }

View File

@ -160,6 +160,9 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
@Qualifier("myOrganizationDaoDstu3") @Qualifier("myOrganizationDaoDstu3")
protected IFhirResourceDao<Organization> myOrganizationDao; protected IFhirResourceDao<Organization> myOrganizationDao;
@Autowired @Autowired
@Qualifier("myConsentDaoDstu3")
protected IFhirResourceDao<Consent> myConsentDao;
@Autowired
protected DatabaseBackedPagingProvider myPagingProvider; protected DatabaseBackedPagingProvider myPagingProvider;
@Autowired @Autowired
@Qualifier("myPatientDaoDstu3") @Qualifier("myPatientDaoDstu3")

View File

@ -1,49 +1,53 @@
package ca.uhn.fhir.jpa.dao.dstu3; package ca.uhn.fhir.jpa.dao.dstu3;
import static org.apache.commons.lang3.StringUtils.defaultString; import ca.uhn.fhir.jpa.dao.*;
import static org.hamcrest.Matchers.*; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
import static org.junit.Assert.*; import ca.uhn.fhir.jpa.entity.TagTypeEnum;
import static org.mockito.Matchers.eq; import ca.uhn.fhir.model.api.Include;
import static org.mockito.Mockito.*; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
import java.util.*; import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.hamcrest.core.StringContains; import org.hamcrest.core.StringContains;
import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.Bundle.*; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.Bundle.BundleType;
import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb;
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus; import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus;
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity; import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity;
import org.hl7.fhir.dstu3.model.OperationOutcome.IssueType; import org.hl7.fhir.dstu3.model.OperationOutcome.IssueType;
import org.hl7.fhir.dstu3.model.Quantity.QuantityComparator; import org.hl7.fhir.dstu3.model.Quantity.QuantityComparator;
import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.*; import org.junit.*;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import com.google.common.collect.Lists; import java.util.*;
import ca.uhn.fhir.jpa.dao.*; import static org.apache.commons.lang3.StringUtils.defaultString;
import ca.uhn.fhir.jpa.entity.*; import static org.hamcrest.Matchers.contains;
import ca.uhn.fhir.model.api.Include; import static org.hamcrest.Matchers.*;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import static org.junit.Assert.*;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; import static org.mockito.Matchers.eq;
import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum; import static org.mockito.Mockito.*;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.TestUtil;
@SuppressWarnings({ "unchecked", "deprecation" }) @SuppressWarnings({"unchecked", "deprecation"})
public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3Test.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3Test.class);
@ -117,7 +121,6 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
} }
} }
private void sortCodings(List<Coding> theSecLabels) { private void sortCodings(List<Coding> theSecLabels) {
Collections.sort(theSecLabels, new Comparator<Coding>() { Collections.sort(theSecLabels, new Comparator<Coding>() {
@Override @Override
@ -479,7 +482,6 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
} }
@Test @Test
public void testCreateDifferentTypesWithSameForcedId() { public void testCreateDifferentTypesWithSameForcedId() {
String idName = "forcedId"; String idName = "forcedId";
@ -500,7 +502,6 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
obs = myObservationDao.read(obsId.toUnqualifiedVersionless(), mySrd); obs = myObservationDao.read(obsId.toUnqualifiedVersionless(), mySrd);
} }
@Test @Test
public void testCreateDuplicateTagsDoesNotCauseDuplicates() { public void testCreateDuplicateTagsDoesNotCauseDuplicates() {
Patient p = new Patient(); Patient p = new Patient();
@ -555,22 +556,22 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
public void testCreateLongString() { public void testCreateLongString() {
//@formatter:off //@formatter:off
String input = "<NamingSystem>\n" + String input = "<NamingSystem>\n" +
" <name value=\"NDF-RT (National Drug File Reference Terminology)\"/>\n" + " <name value=\"NDF-RT (National Drug File Reference Terminology)\"/>\n" +
" <status value=\"draft\"/>\n" + " <status value=\"draft\"/>\n" +
" <kind value=\"codesystem\"/>\n" + " <kind value=\"codesystem\"/>\n" +
" <publisher value=\"HL7, Inc\"/>\n" + " <publisher value=\"HL7, Inc\"/>\n" +
" <date value=\"2015-08-21\"/>\n" + " <date value=\"2015-08-21\"/>\n" +
" <uniqueId>\n" + " <uniqueId>\n" +
" <type value=\"uri\"/>\n" + " <type value=\"uri\"/>\n" +
" <value value=\"http://hl7.org/fhir/ndfrt\"/>\n" + " <value value=\"http://hl7.org/fhir/ndfrt\"/>\n" +
" <preferred value=\"true\"/>\n" + " <preferred value=\"true\"/>\n" +
" </uniqueId>\n" + " </uniqueId>\n" +
" <uniqueId>\n" + " <uniqueId>\n" +
" <type value=\"oid\"/>\n" + " <type value=\"oid\"/>\n" +
" <value value=\"2.16.840.1.113883.6.209\"/>\n" + " <value value=\"2.16.840.1.113883.6.209\"/>\n" +
" <preferred value=\"false\"/>\n" + " <preferred value=\"false\"/>\n" +
" </uniqueId>\n" + " </uniqueId>\n" +
" </NamingSystem>"; " </NamingSystem>";
//@formatter:on //@formatter:on
NamingSystem res = myFhirCtx.newXmlParser().parseResource(NamingSystem.class, input); NamingSystem res = myFhirCtx.newXmlParser().parseResource(NamingSystem.class, input);
@ -809,7 +810,6 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
} }
} }
@Test @Test
public void testCreateWithInvalidReferenceFailsGracefully() { public void testCreateWithInvalidReferenceFailsGracefully() {
Patient patient = new Patient(); Patient patient = new Patient();
@ -1088,7 +1088,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
patient.addIdentifier().setSystem("ZZZZZZZ").setValue("ZZZZZZZZZ"); patient.addIdentifier().setSystem("ZZZZZZZ").setValue("ZZZZZZZZZ");
id2b = myPatientDao.update(patient, mySrd).getId(); id2b = myPatientDao.update(patient, mySrd).getId();
} }
ourLog.info("ID1:{} ID2:{} ID2b:{}", new Object[] { id1, id2, id2b }); ourLog.info("ID1:{} ID2:{} ID2b:{}", new Object[] {id1, id2, id2b});
SearchParameterMap params = new SearchParameterMap(); SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronous(true); params.setLoadSynchronous(true);
@ -1697,7 +1697,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
preDates.add(new Date()); preDates.add(new Date());
Thread.sleep(100); Thread.sleep(100);
patient.setId(id); patient.setId(id);
patient.getName().get(0).getFamilyElement().setValue(methodName + "_i"+i); patient.getName().get(0).getFamilyElement().setValue(methodName + "_i" + i);
ids.add(myPatientDao.update(patient, mySrd).getId().toUnqualified().getValue()); ids.add(myPatientDao.update(patient, mySrd).getId().toUnqualified().getValue());
} }
@ -2057,7 +2057,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
//@formatter:off //@formatter:off
String inputStr = String inputStr =
"{" + "{" +
" \"resourceType\":\"Organization\",\n" + " \"resourceType\":\"Organization\",\n" +
" \"extension\":[\n" + " \"extension\":[\n" +
" {\n" + " {\n" +
@ -2131,7 +2131,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
dr01.setSubject(new Reference(patientId01)); dr01.setSubject(new Reference(patientId01));
IIdType drId01 = myDiagnosticReportDao.create(dr01, mySrd).getId(); IIdType drId01 = myDiagnosticReportDao.create(dr01, mySrd).getId();
ourLog.info("P1[{}] P2[{}] O1[{}] O2[{}] D1[{}]", new Object[] { patientId01, patientId02, obsId01, obsId02, drId01 }); ourLog.info("P1[{}] P2[{}] O1[{}] O2[{}] D1[{}]", new Object[] {patientId01, patientId02, obsId01, obsId02, drId01});
List<Observation> result = toList(myObservationDao.search(new SearchParameterMap(Observation.SP_SUBJECT, new ReferenceParam(patientId01.getIdPart())).setLoadSynchronous(true))); List<Observation> result = toList(myObservationDao.search(new SearchParameterMap(Observation.SP_SUBJECT, new ReferenceParam(patientId01.getIdPart())).setLoadSynchronous(true)));
assertEquals(1, result.size()); assertEquals(1, result.size());
@ -2141,7 +2141,8 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
assertEquals(1, result.size()); assertEquals(1, result.size());
assertEquals(obsId02.getIdPart(), result.get(0).getIdElement().getIdPart()); assertEquals(obsId02.getIdPart(), result.get(0).getIdElement().getIdPart());
result = toList(myObservationDao.search(new SearchParameterMap(Observation.SP_SUBJECT, new ReferenceParam("999999999999")).setLoadSynchronous(true)));; result = toList(myObservationDao.search(new SearchParameterMap(Observation.SP_SUBJECT, new ReferenceParam("999999999999")).setLoadSynchronous(true)));
;
assertEquals(0, result.size()); assertEquals(0, result.size());
} }
@ -2224,7 +2225,8 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
long id = outcome.getId().getIdPartAsLong(); long id = outcome.getId().getIdPartAsLong();
TokenParam value = new TokenParam("urn:system", "001testPersistSearchParams"); TokenParam value = new TokenParam("urn:system", "001testPersistSearchParams");
List<Patient> found = toList(myPatientDao.search(new SearchParameterMap(Patient.SP_IDENTIFIER, value).setLoadSynchronous(true)));; List<Patient> found = toList(myPatientDao.search(new SearchParameterMap(Patient.SP_IDENTIFIER, value).setLoadSynchronous(true)));
;
assertEquals(1, found.size()); assertEquals(1, found.size());
assertEquals(id, found.get(0).getIdElement().getIdPartAsLong().longValue()); assertEquals(id, found.get(0).getIdElement().getIdPartAsLong().longValue());
@ -3535,6 +3537,16 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
} }
/**
* Make sure this can upload successfully (indexer failed at one point)
*/
@Test
public void testUploadConsentWithSourceAttachment() {
Consent consent = new Consent();
consent.setSource(new Attachment().setUrl("http://foo"));
myConsentDao.create(consent);
}
@AfterClass @AfterClass
public static void afterClassClearContext() { public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest(); TestUtil.clearAllStaticFieldsForUnitTest();
@ -3542,7 +3554,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
public static void assertConflictException(ResourceVersionConflictException e) { public static void assertConflictException(ResourceVersionConflictException e) {
assertThat(e.getMessage(), matchesPattern( assertThat(e.getMessage(), matchesPattern(
"Unable to delete [a-zA-Z]+/[0-9]+ because at least one resource has a reference to this resource. First reference found was resource [a-zA-Z]+/[0-9]+ in path [a-zA-Z]+.[a-zA-Z]+")); "Unable to delete [a-zA-Z]+/[0-9]+ because at least one resource has a reference to this resource. First reference found was resource [a-zA-Z]+/[0-9]+ in path [a-zA-Z]+.[a-zA-Z]+"));
} }
private static List<String> toStringList(List<UriType> theUriType) { private static List<String> toStringList(List<UriType> theUriType) {

View File

@ -212,6 +212,9 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
@Qualifier("myStructureDefinitionDaoR4") @Qualifier("myStructureDefinitionDaoR4")
protected IFhirResourceDao<StructureDefinition> myStructureDefinitionDao; protected IFhirResourceDao<StructureDefinition> myStructureDefinitionDao;
@Autowired @Autowired
@Qualifier("myConsentDaoR4")
protected IFhirResourceDao<Consent> myConsentDao;
@Autowired
@Qualifier("mySubscriptionDaoR4") @Qualifier("mySubscriptionDaoR4")
protected IFhirResourceDaoSubscription<Subscription> mySubscriptionDao; protected IFhirResourceDaoSubscription<Subscription> mySubscriptionDao;
@Autowired @Autowired

View File

@ -21,6 +21,7 @@ import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.hamcrest.core.StringContains; import org.hamcrest.core.StringContains;
import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IAnyResource;
@ -3738,6 +3739,26 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
} }
/**
* Make sure this can upload successfully (indexer failed at one point)
*/
@Test
public void testUploadConsentWithSourceAttachment() {
Consent consent = new Consent();
consent.setSource(new Attachment().setUrl("http://foo"));
myConsentDao.create(consent);
}
/**
* Make sure this can upload successfully (indexer failed at one point)
*/
@Test
public void testUploadExtensionStructureDefinition() {
StructureDefinition ext = myValidationSupport.fetchStructureDefinition(myFhirCtx, "http://hl7.org/fhir/StructureDefinition/familymemberhistory-type");
Validate.notNull(ext);
myStructureDefinitionDao.update(ext);
}
@AfterClass @AfterClass
public static void afterClassClearContext() { public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest(); TestUtil.clearAllStaticFieldsForUnitTest();

View File

@ -86,7 +86,7 @@ public class FhirTesterConfig {
.withName("Health Intersections (DSTU2 FHIR)") .withName("Health Intersections (DSTU2 FHIR)")
.addServer() .addServer()
.withId("spark2") .withId("spark2")
.withFhirVersion(FhirVersionEnum.DSTU2) .withFhirVersion(FhirVersionEnum.DSTU3)
.withBaseUrl("http://vonk.furore.com/") .withBaseUrl("http://vonk.furore.com/")
.withName("Vonk - Furore (STU3 FHIR)"); .withName("Vonk - Furore (STU3 FHIR)");

View File

@ -29,7 +29,7 @@
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level> <level>INFO</level>
</filter> </filter>
<file>${fhir.logdir}/fhirtest.log</file> <file>${fhir.logdir}/fhirtest.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

View File

@ -19,42 +19,62 @@ package ca.uhn.fhir.rest.server;
* limitations under the License. * limitations under the License.
* #L% * #L%
*/ */
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.jar.Manifest;
import javax.servlet.ServletException;
import javax.servlet.UnavailableException;
import javax.servlet.http.*;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.ProvidedResourceScanner;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.api.AddProfileTagEnum;
import ca.uhn.fhir.context.api.BundleInclusionRule;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.Destroy;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Initialize;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.server.IFhirVersionServer;
import ca.uhn.fhir.rest.api.server.IRestfulServer;
import ca.uhn.fhir.rest.api.server.ParseAction;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.RestfulServerUtils.ResponseEncoding;
import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
import ca.uhn.fhir.rest.server.method.ConformanceMethodBinding;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.rest.server.tenant.ITenantIdentificationStrategy; import ca.uhn.fhir.rest.server.tenant.ITenantIdentificationStrategy;
import ca.uhn.fhir.util.*;
import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.context.*; import javax.servlet.ServletException;
import ca.uhn.fhir.context.api.AddProfileTagEnum; import javax.servlet.UnavailableException;
import ca.uhn.fhir.context.api.BundleInclusionRule; import javax.servlet.http.HttpServlet;
import ca.uhn.fhir.parser.IParser; import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.rest.annotation.*; import javax.servlet.http.HttpServletResponse;
import ca.uhn.fhir.rest.api.*; import java.io.Closeable;
import ca.uhn.fhir.rest.api.server.*; import java.io.IOException;
import ca.uhn.fhir.rest.server.RestfulServerUtils.ResponseEncoding; import java.io.InputStream;
import ca.uhn.fhir.rest.server.exceptions.*; import java.io.Writer;
import ca.uhn.fhir.rest.server.interceptor.*; import java.lang.annotation.Annotation;
import ca.uhn.fhir.rest.server.method.*; import java.lang.reflect.Method;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import java.lang.reflect.Modifier;
import ca.uhn.fhir.util.*; import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.jar.Manifest;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public class RestfulServer extends HttpServlet implements IRestfulServer<ServletRequestDetails> { public class RestfulServer extends HttpServlet implements IRestfulServer<ServletRequestDetails> {
@ -96,7 +116,9 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
private BaseMethodBinding<?> myServerConformanceMethod; private BaseMethodBinding<?> myServerConformanceMethod;
private Object myServerConformanceProvider; private Object myServerConformanceProvider;
private String myServerName = "HAPI FHIR Server"; private String myServerName = "HAPI FHIR Server";
/** This is configurable but by default we just use HAPI version */ /**
* This is configurable but by default we just use HAPI version
*/
private String myServerVersion = VersionUtil.getVersion(); private String myServerVersion = VersionUtil.getVersion();
private boolean myStarted; private boolean myStarted;
private Map<String, IResourceProvider> myTypeToProvider = new HashMap<>(); private Map<String, IResourceProvider> myTypeToProvider = new HashMap<>();
@ -121,10 +143,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
myFhirContext = theCtx; myFhirContext = theCtx;
} }
private static boolean partIsOperation(String nextString) {
return nextString.length() > 0 && (nextString.charAt(0) == '_' || nextString.charAt(0) == '$' || nextString.equals(Constants.URL_TOKEN_METADATA));
}
private void addContentLocationHeaders(RequestDetails theRequest, HttpServletResponse servletResponse, MethodOutcome response, String resourceName) { private void addContentLocationHeaders(RequestDetails theRequest, HttpServletResponse servletResponse, MethodOutcome response, String resourceName) {
if (response != null && response.getId() != null) { if (response != null && response.getId() != null) {
addLocationHeader(theRequest, servletResponse, response, Constants.HEADER_LOCATION, resourceName); addLocationHeader(theRequest, servletResponse, response, Constants.HEADER_LOCATION, resourceName);
@ -140,15 +158,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
* </p> * </p>
*/ */
public void addHeadersToResponse(HttpServletResponse theHttpResponse) { public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
StringBuilder b = new StringBuilder(); String b = createPoweredByHeader();
b.append("HAPI FHIR "); theHttpResponse.addHeader("X-Powered-By", b);
b.append(VersionUtil.getVersion());
b.append(" REST Server (FHIR Server; FHIR ");
b.append(myFhirContext.getVersion().getVersion().getFhirVersionString());
b.append('/');
b.append(myFhirContext.getVersion().getVersion().name());
b.append(")");
theHttpResponse.addHeader("X-Powered-By", b.toString());
} }
private void addLocationHeader(RequestDetails theRequest, HttpServletResponse theResponse, MethodOutcome response, String headerLocation, String resourceName) { private void addLocationHeader(RequestDetails theRequest, HttpServletResponse theResponse, MethodOutcome response, String headerLocation, String resourceName) {
@ -198,6 +209,33 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
return result; return result;
} }
protected List<String> createPoweredByAttributes() {
return Lists.newArrayList("FHIR Server", "FHIR " + myFhirContext.getVersion().getVersion().getFhirVersionString() + "/" + myFhirContext.getVersion().getVersion().name());
}
protected String createPoweredByHeader() {
StringBuilder b = new StringBuilder();
b.append(createPoweredByHeaderProductName());
b.append(" ");
b.append(VersionUtil.getVersion());
b.append(" REST Server (");
List<String> poweredByAttributes = createPoweredByAttributes();
for (ListIterator<String> iter = poweredByAttributes.listIterator(); iter.hasNext(); ) {
if (iter.nextIndex() > 0) {
b.append("; ");
}
b.append(iter.next());
}
b.append(")");
return b.toString();
}
protected String createPoweredByHeaderProductName() {
return "HAPI FHIR";
}
@Override @Override
public void destroy() { public void destroy() {
if (getResourceProviders() != null) { if (getResourceProviders() != null) {
@ -253,6 +291,31 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
return resourceMethod; return resourceMethod;
} }
@Override
protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(RequestTypeEnum.DELETE, request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(RequestTypeEnum.GET, request, response);
}
@Override
protected void doOptions(HttpServletRequest theReq, HttpServletResponse theResp) throws ServletException, IOException {
handleRequest(RequestTypeEnum.OPTIONS, theReq, theResp);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(RequestTypeEnum.POST, request, response);
}
@Override
protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(RequestTypeEnum.PUT, request, response);
}
/** /**
* Count length of URL string, but treating unescaped sequences (e.g. ' ') as their unescaped equivalent (%20) * Count length of URL string, but treating unescaped sequences (e.g. ' ') as their unescaped equivalent (%20)
*/ */
@ -356,7 +419,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/** /**
* @deprecated As of HAPI FHIR 1.5, this property has been moved to * @deprecated As of HAPI FHIR 1.5, this property has been moved to
* {@link FhirContext#setAddProfileTagWhenEncoding(AddProfileTagEnum)} * {@link FhirContext#setAddProfileTagWhenEncoding(AddProfileTagEnum)}
*/ */
@Override @Override
@Deprecated @Deprecated
@ -369,10 +432,9 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
* (which is the default), the server will automatically add a profile tag based on * (which is the default), the server will automatically add a profile tag based on
* the class of the resource(s) being returned. * the class of the resource(s) being returned.
* *
* @param theAddProfileTag * @param theAddProfileTag The behaviour enum (must not be null)
* The behaviour enum (must not be null)
* @deprecated As of HAPI FHIR 1.5, this property has been moved to * @deprecated As of HAPI FHIR 1.5, this property has been moved to
* {@link FhirContext#setAddProfileTagWhenEncoding(AddProfileTagEnum)} * {@link FhirContext#setAddProfileTagWhenEncoding(AddProfileTagEnum)}
*/ */
@Deprecated @Deprecated
@CoverageIgnore @CoverageIgnore
@ -389,8 +451,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/** /**
* Set how bundle factory should decide whether referenced resources should be included in bundles * Set how bundle factory should decide whether referenced resources should be included in bundles
* *
* @param theBundleInclusionRule * @param theBundleInclusionRule - inclusion rule (@see BundleInclusionRule for behaviors)
* - inclusion rule (@see BundleInclusionRule for behaviors)
*/ */
public void setBundleInclusionRule(BundleInclusionRule theBundleInclusionRule) { public void setBundleInclusionRule(BundleInclusionRule theBundleInclusionRule) {
myBundleInclusionRule = theBundleInclusionRule; myBundleInclusionRule = theBundleInclusionRule;
@ -429,8 +490,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
* Sets (enables/disables) the server support for ETags. Must not be <code>null</code>. Default is * Sets (enables/disables) the server support for ETags. Must not be <code>null</code>. Default is
* {@link #DEFAULT_ETAG_SUPPORT} * {@link #DEFAULT_ETAG_SUPPORT}
* *
* @param theETagSupport * @param theETagSupport The ETag support mode
* The ETag support mode
*/ */
public void setETagSupport(ETagSupportEnum theETagSupport) { public void setETagSupport(ETagSupportEnum theETagSupport) {
if (theETagSupport == null) { if (theETagSupport == null) {
@ -477,8 +537,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/** /**
* Sets (or clears) the list of interceptors * Sets (or clears) the list of interceptors
* *
* @param theList * @param theList The list of interceptors (may be null)
* The list of interceptors (may be null)
*/ */
public void setInterceptors(IServerInterceptor... theList) { public void setInterceptors(IServerInterceptor... theList) {
myInterceptors.clear(); myInterceptors.clear();
@ -487,19 +546,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
} }
} }
/**
* Sets (or clears) the list of interceptors
*
* @param theList
* The list of interceptors (may be null)
*/
public void setInterceptors(List<IServerInterceptor> theList) {
myInterceptors.clear();
if (theList != null) {
myInterceptors.addAll(theList);
}
}
@Override @Override
public IPagingProvider getPagingProvider() { public IPagingProvider getPagingProvider() {
return myPagingProvider; return myPagingProvider;
@ -533,25 +579,13 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
} }
} }
/**
* Sets the non-resource specific providers which implement method calls on this server.
*
* @see #setResourceProviders(Collection)
*/
public void setPlainProviders(Object... theProv) {
setPlainProviders(Arrays.asList(theProv));
}
/** /**
* Allows users of RestfulServer to override the getRequestPath method to let them build their custom request path * Allows users of RestfulServer to override the getRequestPath method to let them build their custom request path
* implementation * implementation
* *
* @param requestFullPath * @param requestFullPath the full request path
* the full request path * @param servletContextPath the servelet context path
* @param servletContextPath * @param servletPath the servelet path
* the servelet context path
* @param servletPath
* the servelet path
* @return created resource path * @return created resource path
*/ */
protected String getRequestPath(String requestFullPath, String servletContextPath, String servletPath) { protected String getRequestPath(String requestFullPath, String servletContextPath, String servletPath) {
@ -579,16 +613,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
} }
} }
/**
* Sets the resource providers for this server
*/
public void setResourceProviders(IResourceProvider... theResourceProviders) {
myResourceProviders.clear();
if (theResourceProviders != null) {
myResourceProviders.addAll(Arrays.asList(theResourceProviders));
}
}
/** /**
* Get the server address strategy, which is used to determine what base URL to provide clients to refer to this * Get the server address strategy, which is used to determine what base URL to provide clients to refer to this
* server. Defaults to an instance of {@link IncomingRequestAddressStrategy} * server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
@ -608,6 +632,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/** /**
* Returns the server base URL (with no trailing '/') for a given request * Returns the server base URL (with no trailing '/') for a given request
*
* @param theRequest * @param theRequest
*/ */
public String getServerBaseForRequest(ServletRequestDetails theRequest) { public String getServerBaseForRequest(ServletRequestDetails theRequest) {
@ -656,9 +681,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
* </p> * </p>
* Note that this method can only be called before the server is initialized. * Note that this method can only be called before the server is initialized.
* *
* @throws IllegalStateException * @throws IllegalStateException Note that this method can only be called prior to {@link #init() initialization} and will throw an
* Note that this method can only be called prior to {@link #init() initialization} and will throw an * {@link IllegalStateException} if called after that.
* {@link IllegalStateException} if called after that.
*/ */
public void setServerConformanceProvider(Object theServerConformanceProvider) { public void setServerConformanceProvider(Object theServerConformanceProvider) {
if (myStarted) { if (myStarted) {
@ -670,9 +694,9 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
// passing the server into the constructor. Having that sort // passing the server into the constructor. Having that sort
// of cross linkage causes reference cycles in Spring wiring // of cross linkage causes reference cycles in Spring wiring
try { try {
Method setRestfulServer = theServerConformanceProvider.getClass().getMethod("setRestfulServer", new Class[] { RestfulServer.class }); Method setRestfulServer = theServerConformanceProvider.getClass().getMethod("setRestfulServer", new Class[] {RestfulServer.class});
if (setRestfulServer != null) { if (setRestfulServer != null) {
setRestfulServer.invoke(theServerConformanceProvider, new Object[] { this }); setRestfulServer.invoke(theServerConformanceProvider, new Object[] {this});
} }
} catch (Exception e) { } catch (Exception e) {
ourLog.warn("Error calling IServerConformanceProvider.setRestfulServer", e); ourLog.warn("Error calling IServerConformanceProvider.setRestfulServer", e);
@ -680,15 +704,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
myServerConformanceProvider = theServerConformanceProvider; myServerConformanceProvider = theServerConformanceProvider;
} }
/**
* If provided (default is <code>null</code>), the tenant identification
* strategy provides a mechanism for a multitenant server to identify which tenant
* a given request corresponds to.
*/
public void setTenantIdentificationStrategy(ITenantIdentificationStrategy theTenantIdentificationStrategy) {
myTenantIdentificationStrategy = theTenantIdentificationStrategy;
}
/** /**
* Gets the server's name, as exported in conformance profiles exported by the server. This is informational only, * Gets the server's name, as exported in conformance profiles exported by the server. This is informational only,
* but can be helpful to set with something appropriate. * but can be helpful to set with something appropriate.
@ -1029,10 +1044,9 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
* This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the * This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the
* server being used. * server being used.
* *
* @throws ServletException * @throws ServletException If the initialization failed. Note that you should consider throwing {@link UnavailableException}
* If the initialization failed. Note that you should consider throwing {@link UnavailableException} * (which extends {@link ServletException}), as this is a flag to the servlet container
* (which extends {@link ServletException}), as this is a flag to the servlet container * that the servlet is not usable.
* that the servlet is not usable.
*/ */
protected void initialize() throws ServletException { protected void initialize() throws ServletException {
// nothing by default // nothing by default
@ -1056,6 +1070,24 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
} }
} }
private void invokeInitialize(Object theProvider) {
invokeInitialize(theProvider, theProvider.getClass());
}
private void invokeInitialize(Object theProvider, Class<?> clazz) {
for (Method m : ReflectionUtil.getDeclaredMethods(clazz)) {
Initialize initialize = m.getAnnotation(Initialize.class);
if (initialize != null) {
invokeInitializeOrDestroyMethod(theProvider, m, "initialize");
}
}
Class<?> supertype = clazz.getSuperclass();
if (!Object.class.equals(supertype)) {
invokeInitialize(theProvider, supertype);
}
}
private void invokeInitializeOrDestroyMethod(Object theProvider, Method m, String theMethodDescription) { private void invokeInitializeOrDestroyMethod(Object theProvider, Method m, String theMethodDescription) {
Class<?>[] paramTypes = m.getParameterTypes(); Class<?>[] paramTypes = m.getParameterTypes();
@ -1078,24 +1110,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
} }
} }
private void invokeInitialize(Object theProvider) {
invokeInitialize(theProvider, theProvider.getClass());
}
private void invokeInitialize(Object theProvider, Class<?> clazz) {
for (Method m : ReflectionUtil.getDeclaredMethods(clazz)) {
Initialize initialize = m.getAnnotation(Initialize.class);
if (initialize != null) {
invokeInitializeOrDestroyMethod(theProvider, m, "initialize");
}
}
Class<?> supertype = clazz.getSuperclass();
if (!Object.class.equals(supertype)) {
invokeInitialize(theProvider, supertype);
}
}
/** /**
* Should the server "pretty print" responses by default (requesting clients can always override this default by * Should the server "pretty print" responses by default (requesting clients can always override this default by
* supplying an <code>Accept</code> header in the request, or a <code>_pretty</code> * supplying an <code>Accept</code> header in the request, or a <code>_pretty</code>
@ -1119,8 +1133,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
* The default is <code>false</code> * The default is <code>false</code>
* </p> * </p>
* *
* @param theDefaultPrettyPrint * @param theDefaultPrettyPrint The default pretty print setting
* The default pretty print setting
*/ */
public void setDefaultPrettyPrint(boolean theDefaultPrettyPrint) { public void setDefaultPrettyPrint(boolean theDefaultPrettyPrint) {
myDefaultPrettyPrint = theDefaultPrettyPrint; myDefaultPrettyPrint = theDefaultPrettyPrint;
@ -1174,8 +1187,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/** /**
* @deprecated This feature did not work well, and will be removed. Use {@link ResponseHighlighterInterceptor} * @deprecated This feature did not work well, and will be removed. Use {@link ResponseHighlighterInterceptor}
* instead as an interceptor on your server and it will provide more useful syntax * instead as an interceptor on your server and it will provide more useful syntax
* highlighting. Deprocated in 1.4 * highlighting. Deprocated in 1.4
*/ */
@Deprecated @Deprecated
@Override @Override
@ -1185,8 +1198,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/** /**
* @deprecated This feature did not work well, and will be removed. Use {@link ResponseHighlighterInterceptor} * @deprecated This feature did not work well, and will be removed. Use {@link ResponseHighlighterInterceptor}
* instead as an interceptor on your server and it will provide more useful syntax * instead as an interceptor on your server and it will provide more useful syntax
* highlighting. Deprocated in 1.4 * highlighting. Deprocated in 1.4
*/ */
@Deprecated @Deprecated
public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) { public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
@ -1285,8 +1298,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/** /**
* Register a group of providers. These could be Resource Providers, "plain" providers or a mixture of the two. * Register a group of providers. These could be Resource Providers, "plain" providers or a mixture of the two.
* *
* @param providers * @param providers a {@code Collection} of providers. The parameter could be null or an empty {@code Collection}
* a {@code Collection} of providers. The parameter could be null or an empty {@code Collection}
*/ */
public void registerProviders(Collection<? extends Object> providers) { public void registerProviders(Collection<? extends Object> providers) {
myProviderRegistrationMutex.lock(); myProviderRegistrationMutex.lock();
@ -1327,7 +1339,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
String resourceName = getFhirContext().getResourceDefinition(resourceType).getName(); String resourceName = getFhirContext().getResourceDefinition(resourceType).getName();
if (myTypeToProvider.containsKey(resourceName)) { if (myTypeToProvider.containsKey(resourceName)) {
throw new ConfigurationException("Multiple resource providers return resource type[" + resourceName + "]: First[" + myTypeToProvider.get(resourceName).getClass().getCanonicalName() throw new ConfigurationException("Multiple resource providers return resource type[" + resourceName + "]: First[" + myTypeToProvider.get(resourceName).getClass().getCanonicalName()
+ "] and Second[" + rsrcProvider.getClass().getCanonicalName() + "]"); + "] and Second[" + rsrcProvider.getClass().getCanonicalName() + "]");
} }
if (!inInit) { if (!inInit) {
myResourceProviders.add(rsrcProvider); myResourceProviders.add(rsrcProvider);
@ -1376,7 +1388,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/* /*
* Remove registered RESTful methods for a Provider (and all superclasses) when it is being unregistered * Remove registered RESTful methods for a Provider (and all superclasses) when it is being unregistered
*/ */
private void removeResourceMethods(Object theProvider) throws Exception { private void removeResourceMethods(Object theProvider) {
ourLog.info("Removing RESTful methods for: {}", theProvider.getClass()); ourLog.info("Removing RESTful methods for: {}", theProvider.getClass());
Class<?> clazz = theProvider.getClass(); Class<?> clazz = theProvider.getClass();
Class<?> supertype = clazz.getSuperclass(); Class<?> supertype = clazz.getSuperclass();
@ -1447,50 +1459,46 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
} }
switch (method) { switch (method) {
case DELETE: case DELETE:
doDelete(theReq, theResp); doDelete(theReq, theResp);
break; break;
case GET: case GET:
doGet(theReq, theResp); doGet(theReq, theResp);
break; break;
case OPTIONS: case OPTIONS:
doOptions(theReq, theResp); doOptions(theReq, theResp);
break; break;
case POST: case POST:
doPost(theReq, theResp); doPost(theReq, theResp);
break; break;
case PUT: case PUT:
doPut(theReq, theResp); doPut(theReq, theResp);
break; break;
default: default:
handleRequest(method, theReq, theResp); handleRequest(method, theReq, theResp);
break; break;
} }
} }
@Override /**
protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { * Sets (or clears) the list of interceptors
handleRequest(RequestTypeEnum.DELETE, request, response); *
* @param theList The list of interceptors (may be null)
*/
public void setInterceptors(List<IServerInterceptor> theList) {
myInterceptors.clear();
if (theList != null) {
myInterceptors.addAll(theList);
}
} }
@Override /**
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { * Sets the non-resource specific providers which implement method calls on this server.
handleRequest(RequestTypeEnum.GET, request, response); *
} * @see #setResourceProviders(Collection)
*/
@Override public void setPlainProviders(Object... theProv) {
protected void doOptions(HttpServletRequest theReq, HttpServletResponse theResp) throws ServletException, IOException { setPlainProviders(Arrays.asList(theProv));
handleRequest(RequestTypeEnum.OPTIONS, theReq, theResp);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(RequestTypeEnum.POST, request, response);
}
@Override
protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(RequestTypeEnum.PUT, request, response);
} }
/** /**
@ -1505,6 +1513,25 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
} }
} }
/**
* Sets the resource providers for this server
*/
public void setResourceProviders(IResourceProvider... theResourceProviders) {
myResourceProviders.clear();
if (theResourceProviders != null) {
myResourceProviders.addAll(Arrays.asList(theResourceProviders));
}
}
/**
* If provided (default is <code>null</code>), the tenant identification
* strategy provides a mechanism for a multitenant server to identify which tenant
* a given request corresponds to.
*/
public void setTenantIdentificationStrategy(ITenantIdentificationStrategy theTenantIdentificationStrategy) {
myTenantIdentificationStrategy = theTenantIdentificationStrategy;
}
public void unregisterInterceptor(IServerInterceptor theInterceptor) { public void unregisterInterceptor(IServerInterceptor theInterceptor) {
Validate.notNull(theInterceptor, "Interceptor can not be null"); Validate.notNull(theInterceptor, "Interceptor can not be null");
myInterceptors.remove(theInterceptor); myInterceptors.remove(theInterceptor);
@ -1567,6 +1594,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
theResponse.getWriter().write(theException.getMessage()); theResponse.getWriter().write(theException.getMessage());
} }
private static boolean partIsOperation(String nextString) {
return nextString.length() > 0 && (nextString.charAt(0) == '_' || nextString.charAt(0) == '$' || nextString.equals(Constants.URL_TOKEN_METADATA));
}
// /** // /**
// * Returns the read method binding for the given resource type, or // * Returns the read method binding for the given resource type, or
// * returns <code>null</code> if not // * returns <code>null</code> if not

View File

@ -228,6 +228,7 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r4/model/profile/profiles-resources.xml"); loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r4/model/profile/profiles-resources.xml");
loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r4/model/profile/profiles-types.xml"); loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r4/model/profile/profiles-types.xml");
loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r4/model/profile/profiles-others.xml"); loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r4/model/profile/profiles-others.xml");
loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r4/model/extension/extension-definitions.xml");
myStructureDefinitions = structureDefinitions; myStructureDefinitions = structureDefinitions;
} }

View File

@ -38,6 +38,7 @@ import org.mockito.stubbing.Answer;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
@ -411,11 +412,8 @@ public class FhirInstanceValidatorR4Test {
ourLog.info("Took {} ms -- {}ms / pass", delay, per); ourLog.info("Took {} ms -- {}ms / pass", delay, per);
} }
/**
* // TODO: reenable
*/
@Test @Test
@Ignore @Ignore
public void testValidateBuiltInProfiles() throws Exception { public void testValidateBuiltInProfiles() throws Exception {
org.hl7.fhir.r4.model.Bundle bundle; org.hl7.fhir.r4.model.Bundle bundle;
String name = "profiles-resources"; String name = "profiles-resources";
@ -442,16 +440,23 @@ public class FhirInstanceValidatorR4Test {
ourLog.trace(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(next)); ourLog.trace(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(next));
ValidationResult output = myVal.validateWithResult(next); ValidationResult output = myVal.validateWithResult(next);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output); List<SingleValidationMessage> results = logResultsAndReturnAll(output);
// This isn't a validator problem but a definition problem.. it should get fixed at some point and // This isn't a validator problem but a definition problem.. it should get fixed at some point and
// we can remove this // we can remove this. Tracker #17207 was filed about this
if (next.getId().equalsIgnoreCase("http://hl7.org/fhir/OperationDefinition/StructureDefinition-generate")) { // https://gforge.hl7.org/gf/project/fhir/tracker/?action=TrackerItemEdit&tracker_item_id=17207
assertEquals(1, errors.size()); if (next.getId().equalsIgnoreCase("http://hl7.org/fhir/OperationDefinition/StructureDefinition-snapshot")) {
assertEquals("A search type can only be specified for parameters of type string [searchType implies type = 'string']", errors.get(0).getMessage()); assertEquals(1, results.size());
assertEquals("A search type can only be specified for parameters of type string [searchType.exists() implies type = 'string']", results.get(0).getMessage());
continue; continue;
} }
List<SingleValidationMessage> errors = results
.stream()
.filter(t -> t.getSeverity() != ResultSeverityEnum.INFORMATION)
.collect(Collectors.toList());
assertThat("Failed to validate " + i.getFullUrl() + " - " + errors, errors, empty()); assertThat("Failed to validate " + i.getFullUrl() + " - " + errors, errors, empty());
} }

View File

@ -185,34 +185,52 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin>
<groupId>org.apache.karaf.tooling</groupId>
<artifactId>karaf-maven-plugin</artifactId>
<version>${apache_karaf_version}</version>
<configuration>
<descriptors>
<descriptor>file://${project.build.directory}/classes/${features.file}</descriptor>
<descriptor>mvn:org.apache.karaf.features/enterprise/${apache_karaf_version}/xml/features</descriptor>
</descriptors>
<distribution>org.apache.karaf.features:framework</distribution>
<javase>1.8</javase>
<framework>
<feature>framework</feature>
</framework>
<features>
<feature>hapi-fhir*</feature>
</features>
</configuration>
<executions>
<execution>
<id>validate</id>
<phase>process-resources</phase>
<goals>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins> </plugins>
</build> </build>
<profiles>
<!--
The karaf verification step doesn't happen by default
because it doesn't seem to work on Windows.
See https://github.com/jamesagnew/hapi-fhir/issues/921
-->
<profile>
<id>DIST</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.karaf.tooling</groupId>
<artifactId>karaf-maven-plugin</artifactId>
<version>${apache_karaf_version}</version>
<configuration>
<descriptors>
<descriptor>file://${project.build.directory}/classes/${features.file}</descriptor>
<descriptor>mvn:org.apache.karaf.features/enterprise/${apache_karaf_version}/xml/features</descriptor>
</descriptors>
<distribution>org.apache.karaf.features:framework</distribution>
<javase>1.8</javase>
<framework>
<feature>framework</feature>
</framework>
<features>
<feature>hapi-fhir*</feature>
</features>
</configuration>
<executions>
<execution>
<id>validate</id>
<phase>process-resources</phase>
<goals>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project> </project>

View File

@ -172,6 +172,18 @@
invocation at the type level as being at the instance level if the method invocation at the type level as being at the instance level if the method
indicated that the IdParam parameter was optional. This has been fixed. indicated that the IdParam parameter was optional. This has been fixed.
</action> </action>
<action type="add">
StructureDefinitions for the FHIR standard extensions have been added to the
hapi-fhir-validation-resources-XXXX modules. Thanks to Patrick Werner for the
pull request! These have also been added to the list of definitions uploaded
by the CLI "upload-definitions" command.
</action>
<action type="fix">
A workaround for an invalid search parameter path in the R4 consent
resource has been implemented. This path was preventing some Consent
resources from successfully being uploaded to the JPA server. Thanks to
Anthony Sute for identifying this.
</action>
</release> </release>
<release version="3.3.0" date="2018-03-29"> <release version="3.3.0" date="2018-03-29">
<action type="add"> <action type="add">

View File

@ -60,3 +60,4 @@ cp ~/workspace/fhir/trunk/build/publish/profiles-*.xml hapi-fhir-validatio
cp ~/workspace/fhir/trunk/build/publish/v2-tables.xml hapi-fhir-validation-resources-r4/src/main/resources/org/hl7/fhir/r4/model/valueset/ cp ~/workspace/fhir/trunk/build/publish/v2-tables.xml hapi-fhir-validation-resources-r4/src/main/resources/org/hl7/fhir/r4/model/valueset/
cp ~/workspace/fhir/trunk/build/publish/v3-codesystems.xml hapi-fhir-validation-resources-r4/src/main/resources/org/hl7/fhir/r4/model/valueset/ cp ~/workspace/fhir/trunk/build/publish/v3-codesystems.xml hapi-fhir-validation-resources-r4/src/main/resources/org/hl7/fhir/r4/model/valueset/
cp ~/workspace/fhir/trunk/build/publish/valuesets.xml hapi-fhir-validation-resources-r4/src/main/resources/org/hl7/fhir/r4/model/valueset/ cp ~/workspace/fhir/trunk/build/publish/valuesets.xml hapi-fhir-validation-resources-r4/src/main/resources/org/hl7/fhir/r4/model/valueset/
cp ~/workspace/fhir/trunk/build/publish/extension-definitions.xml hapi-fhir-validation-resources-r4/src/main/resources/org/hl7/fhir/r4/model/extension/