Merge branch 'master' into documentOperation

This commit is contained in:
James Agnew 2018-07-19 17:44:46 -04:00 committed by GitHub
commit 8f2d3998f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 3434 additions and 2240 deletions

32
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,32 @@
---
name: Bug report
about: Create a report to help us improve
---
NOTE: Before filing a ticket, please see the following URL:
https://github.com/jamesagnew/hapi-fhir/wiki/Getting-Help
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment (please complete the following information):**
- HAPI FHIR Version
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
**Additional context**
Add any other context about the problem here.

View File

@ -17,3 +17,5 @@ A demonstration of this project is available here:
http://hapi.fhir.org/
This project is Open Source, licensed under the Apache Software License 2.0.
Please see [this wiki page](https://github.com/jamesagnew/hapi-fhir/wiki/Getting-Help) for information on where to get help with HAPI FHIR. Please see [Smile CDR](https://smilecdr.com) for information on commercial support.

View File

@ -1,7 +1,34 @@
package ca.uhn.fhir.context;
import ca.uhn.fhir.context.api.AddProfileTagEnum;
import ca.uhn.fhir.context.support.IContextValidationSupport;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.view.ViewGenerator;
import ca.uhn.fhir.narrative.INarrativeGenerator;
import ca.uhn.fhir.parser.*;
import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.client.api.IRestfulClientFactory;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.ReflectionUtil;
import ca.uhn.fhir.util.VersionUtil;
import ca.uhn.fhir.validation.FhirValidator;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.Map.Entry;
/*
* #%L
@ -23,26 +50,6 @@ import java.lang.reflect.Method;
* #L%
*/
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.Map.Entry;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.context.api.AddProfileTagEnum;
import ca.uhn.fhir.context.support.IContextValidationSupport;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.fhir.model.api.*;
import ca.uhn.fhir.model.view.ViewGenerator;
import ca.uhn.fhir.narrative.INarrativeGenerator;
import ca.uhn.fhir.parser.*;
import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
import ca.uhn.fhir.rest.client.api.*;
import ca.uhn.fhir.util.*;
import ca.uhn.fhir.validation.FhirValidator;
/**
* The FHIR context is the central starting point for the use of the HAPI FHIR API. It should be created once, and then
* used as a factory for various other types of objects (parsers, clients, etc.).
@ -68,6 +75,7 @@ public class FhirContext {
private static final List<Class<? extends IBaseResource>> EMPTY_LIST = Collections.emptyList();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirContext.class);
private final IFhirVersion myVersion;
private AddProfileTagEnum myAddProfileTagWhenEncoding = AddProfileTagEnum.ONLY_FOR_CUSTOM;
private volatile Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinition = Collections.emptyMap();
private ArrayList<Class<? extends IBase>> myCustomTypes;
@ -87,9 +95,6 @@ public class FhirContext {
private volatile IRestfulClientFactory myRestfulClientFactory;
private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
private IContextValidationSupport<?, ?, ?, ?, ?, ?> myValidationSupport;
private final IFhirVersion myVersion;
private Map<FhirVersionEnum, Map<String, Class<? extends IBaseResource>>> myVersionToNameToResourceType = Collections.emptyMap();
/**
@ -208,6 +213,30 @@ public class FhirContext {
return myAddProfileTagWhenEncoding;
}
/**
* When encoding resources, this setting configures the parser to include
* an entry in the resource's metadata section which indicates which profile(s) the
* resource claims to conform to. The default is {@link AddProfileTagEnum#ONLY_FOR_CUSTOM}.
* <p>
* This feature is intended for situations where custom resource types are being used,
* avoiding the need to manually add profile declarations for these custom types.
* </p>
* <p>
* See <a href="http://jamesagnew.gihhub.io/hapi-fhir/doc_extensions.html">Profiling and Extensions</a>
* for more information on using custom types.
* </p>
* <p>
* Note that this feature automatically adds the profile, but leaves any profile tags
* which have been manually added in place as well.
* </p>
*
* @param theAddProfileTagWhenEncoding The add profile mode (must not be <code>null</code>)
*/
public void setAddProfileTagWhenEncoding(AddProfileTagEnum theAddProfileTagWhenEncoding) {
Validate.notNull(theAddProfileTagWhenEncoding, "theAddProfileTagWhenEncoding must not be null");
myAddProfileTagWhenEncoding = theAddProfileTagWhenEncoding;
}
Collection<RuntimeResourceDefinition> getAllResourceDefinitions() {
validateInitialized();
return myNameToResourceDefinition.values();
@ -249,7 +278,9 @@ public class FhirContext {
return myNameToElementDefinition.get(theElementName.toLowerCase());
}
/** For unit tests only */
/**
* For unit tests only
*/
int getElementDefinitionCount() {
validateInitialized();
return myClassToElementDefinition.size();
@ -274,10 +305,22 @@ public class FhirContext {
return myLocalizer;
}
/**
* This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with
* caution
*/
public void setLocalizer(HapiLocalizer theMessages) {
myLocalizer = theMessages;
}
public INarrativeGenerator getNarrativeGenerator() {
return myNarrativeGenerator;
}
public void setNarrativeGenerator(INarrativeGenerator theNarrativeGenerator) {
myNarrativeGenerator = theNarrativeGenerator;
}
/**
* Returns the parser options object which will be used to supply default
* options to newly created parsers
@ -288,6 +331,17 @@ public class FhirContext {
return myParserOptions;
}
/**
* Sets the parser options object which will be used to supply default
* options to newly created parsers
*
* @param theParserOptions The parser options object - Must not be <code>null</code>
*/
public void setParserOptions(ParserOptions theParserOptions) {
Validate.notNull(theParserOptions, "theParserOptions must not be null");
myParserOptions = theParserOptions;
}
/**
* Get the configured performance options
*/
@ -295,6 +349,32 @@ public class FhirContext {
return myPerformanceOptions;
}
// /**
// * Return an unmodifiable collection containing all known resource definitions
// */
// public Collection<RuntimeResourceDefinition> getResourceDefinitions() {
//
// Set<Class<? extends IBase>> datatypes = Collections.emptySet();
// Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> existing = Collections.emptyMap();
// HashMap<String, Class<? extends IBaseResource>> types = new HashMap<String, Class<? extends IBaseResource>>();
// ModelScanner.scanVersionPropertyFile(datatypes, types, myVersion.getVersion(), existing);
// for (int next : types.)
//
// return Collections.unmodifiableCollection(myIdToResourceDefinition.values());
// }
/**
* Sets the configured performance options
*
* @see PerformanceOptionsEnum for a list of available options
*/
public void setPerformanceOptions(Collection<PerformanceOptionsEnum> theOptions) {
myPerformanceOptions.clear();
if (theOptions != null) {
myPerformanceOptions.addAll(theOptions);
}
}
/**
* Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed
* for extending the core library.
@ -359,8 +439,12 @@ public class FhirContext {
* <p>
* Note that this method is case insensitive!
* </p>
*
* @throws DataFormatException If the resource name is not known
*/
public RuntimeResourceDefinition getResourceDefinition(String theResourceName) {
// Multiple spots in HAPI FHIR and Smile CDR depend on DataFormatException being
// thrown by this method, don't change that.
public RuntimeResourceDefinition getResourceDefinition(String theResourceName) throws DataFormatException {
validateInitialized();
Validate.notBlank(theResourceName, "theResourceName must not be blank");
@ -380,20 +464,6 @@ public class FhirContext {
return retVal;
}
// /**
// * Return an unmodifiable collection containing all known resource definitions
// */
// public Collection<RuntimeResourceDefinition> getResourceDefinitions() {
//
// Set<Class<? extends IBase>> datatypes = Collections.emptySet();
// Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> existing = Collections.emptyMap();
// HashMap<String, Class<? extends IBaseResource>> types = new HashMap<String, Class<? extends IBaseResource>>();
// ModelScanner.scanVersionPropertyFile(datatypes, types, myVersion.getVersion(), existing);
// for (int next : types.)
//
// return Collections.unmodifiableCollection(myIdToResourceDefinition.values());
// }
/**
* Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed
* for extending the core library.
@ -412,6 +482,36 @@ public class FhirContext {
return myIdToResourceDefinition.values();
}
/**
* Returns an unmodifiable set containing all resource names known to this
* context
*/
public Set<String> getResourceNames() {
Set<String> resourceNames = new HashSet<>();
if (myNameToResourceDefinition.isEmpty()) {
Properties props = new Properties();
try {
props.load(myVersion.getFhirVersionPropertiesFile());
} catch (IOException theE) {
throw new ConfigurationException("Failed to load version properties file");
}
Enumeration<?> propNames = props.propertyNames();
while (propNames.hasMoreElements()) {
String next = (String) propNames.nextElement();
if (next.startsWith("resource.")) {
resourceNames.add(next.substring("resource.".length()).trim());
}
}
}
for (RuntimeResourceDefinition next : myNameToResourceDefinition.values()) {
resourceNames.add(next.getName());
}
return Collections.unmodifiableSet(resourceNames);
}
/**
* Get the restful client factory. If no factory has been set, this will be initialized with
* a new ApacheRestfulClientFactory.
@ -429,6 +529,16 @@ public class FhirContext {
return myRestfulClientFactory;
}
/**
* Set the restful client factory
*
* @param theRestfulClientFactory
*/
public void setRestfulClientFactory(IRestfulClientFactory theRestfulClientFactory) {
Validate.notNull(theRestfulClientFactory, "theRestfulClientFactory must not be null");
this.myRestfulClientFactory = theRestfulClientFactory;
}
public RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() {
validateInitialized();
return myRuntimeChildUndeclaredExtensionDefinition;
@ -448,6 +558,15 @@ public class FhirContext {
return myValidationSupport;
}
/**
* Sets the validation support module to use for this context. The validation support module
* is used to supply underlying infrastructure such as conformance resources (StructureDefinition, ValueSet, etc)
* as well as to provide terminology services to modules such as the validator and FluentPath executor
*/
public void setValidationSupport(IContextValidationSupport<?, ?, ?, ?, ?, ?> theValidationSupport) {
myValidationSupport = theValidationSupport;
}
public IFhirVersion getVersion() {
return myVersion;
}
@ -519,13 +638,10 @@ public class FhirContext {
* without incurring any performance penalty
* </p>
*
* @param theClientType
* The client type, which is an interface type to be instantiated
* @param theServerBase
* The URL of the base for the restful FHIR server to connect to
* @param theClientType The client type, which is an interface type to be instantiated
* @param theServerBase The URL of the base for the restful FHIR server to connect to
* @return A newly created client
* @throws ConfigurationException
* If the interface type is not an interface
* @throws ConfigurationException If the interface type is not an interface
*/
public <T extends IRestfulClient> T newRestfulClient(Class<T> theClientType, String theServerBase) {
return getRestfulClientFactory().newClient(theClientType, theServerBase);
@ -541,8 +657,7 @@ public class FhirContext {
* without incurring any performance penalty
* </p>
*
* @param theServerBase
* The URL of the base for the restful FHIR server to connect to
* @param theServerBase The URL of the base for the restful FHIR server to connect to
*/
public IGenericClient newRestfulGenericClient(String theServerBase) {
return getRestfulClientFactory().newGenericClient(theServerBase);
@ -593,8 +708,7 @@ public class FhirContext {
* threads are able to call any methods on this context.
* </p>
*
* @param theType
* The custom type to add (must not be <code>null</code>)
* @param theType The custom type to add (must not be <code>null</code>)
*/
public void registerCustomType(Class<? extends IBase> theType) {
Validate.notNull(theType, "theType must not be null");
@ -613,8 +727,7 @@ public class FhirContext {
* threads are able to call any methods on this context.
* </p>
*
* @param theTypes
* The custom types to add (must not be <code>null</code> or contain null elements in the collection)
* @param theTypes The custom types to add (must not be <code>null</code> or contain null elements in the collection)
*/
public void registerCustomTypes(Collection<Class<? extends IBase>> theTypes) {
Validate.notNull(theTypes, "theTypes must not be null");
@ -698,31 +811,6 @@ public class FhirContext {
return classToElementDefinition;
}
/**
* When encoding resources, this setting configures the parser to include
* an entry in the resource's metadata section which indicates which profile(s) the
* resource claims to conform to. The default is {@link AddProfileTagEnum#ONLY_FOR_CUSTOM}.
* <p>
* This feature is intended for situations where custom resource types are being used,
* avoiding the need to manually add profile declarations for these custom types.
* </p>
* <p>
* See <a href="http://jamesagnew.gihhub.io/hapi-fhir/doc_extensions.html">Profiling and Extensions</a>
* for more information on using custom types.
* </p>
* <p>
* Note that this feature automatically adds the profile, but leaves any profile tags
* which have been manually added in place as well.
* </p>
*
* @param theAddProfileTagWhenEncoding
* The add profile mode (must not be <code>null</code>)
*/
public void setAddProfileTagWhenEncoding(AddProfileTagEnum theAddProfileTagWhenEncoding) {
Validate.notNull(theAddProfileTagWhenEncoding, "theAddProfileTagWhenEncoding must not be null");
myAddProfileTagWhenEncoding = theAddProfileTagWhenEncoding;
}
/**
* Sets the default type which will be used when parsing a resource that is found to be
* of the given profile.
@ -733,11 +821,9 @@ public class FhirContext {
* the <code>MyPatient</code> type will be used unless otherwise specified.
* </p>
*
* @param theProfile
* The profile string, e.g. <code>"http://example.com/some_patient_profile"</code>. Must not be
* @param theProfile The profile string, e.g. <code>"http://example.com/some_patient_profile"</code>. Must not be
* <code>null</code> or empty.
* @param theClass
* The resource type, or <code>null</code> to clear any existing type
* @param theClass The resource type, or <code>null</code> to clear any existing type
*/
public void setDefaultTypeForProfile(String theProfile, Class<? extends IBaseResource> theClass) {
Validate.notBlank(theProfile, "theProfile must not be null or empty");
@ -748,53 +834,16 @@ public class FhirContext {
}
}
/**
* This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with
* caution
*/
public void setLocalizer(HapiLocalizer theMessages) {
myLocalizer = theMessages;
}
public void setNarrativeGenerator(INarrativeGenerator theNarrativeGenerator) {
myNarrativeGenerator = theNarrativeGenerator;
}
/**
* Sets a parser error handler to use by default on all parsers
*
* @param theParserErrorHandler
* The error handler
* @param theParserErrorHandler The error handler
*/
public void setParserErrorHandler(IParserErrorHandler theParserErrorHandler) {
Validate.notNull(theParserErrorHandler, "theParserErrorHandler must not be null");
myParserErrorHandler = theParserErrorHandler;
}
/**
* Sets the parser options object which will be used to supply default
* options to newly created parsers
*
* @param theParserOptions
* The parser options object - Must not be <code>null</code>
*/
public void setParserOptions(ParserOptions theParserOptions) {
Validate.notNull(theParserOptions, "theParserOptions must not be null");
myParserOptions = theParserOptions;
}
/**
* Sets the configured performance options
*
* @see PerformanceOptionsEnum for a list of available options
*/
public void setPerformanceOptions(Collection<PerformanceOptionsEnum> theOptions) {
myPerformanceOptions.clear();
if (theOptions != null) {
myPerformanceOptions.addAll(theOptions);
}
}
/**
* Sets the configured performance options
*
@ -808,26 +857,7 @@ public class FhirContext {
setPerformanceOptions(asList);
}
/**
* Set the restful client factory
*
* @param theRestfulClientFactory
*/
public void setRestfulClientFactory(IRestfulClientFactory theRestfulClientFactory) {
Validate.notNull(theRestfulClientFactory, "theRestfulClientFactory must not be null");
this.myRestfulClientFactory = theRestfulClientFactory;
}
/**
* Sets the validation support module to use for this context. The validation support module
* is used to supply underlying infrastructure such as conformance resources (StructureDefinition, ValueSet, etc)
* as well as to provide terminology services to modules such as the validator and FluentPath executor
*/
public void setValidationSupport(IContextValidationSupport<?, ?, ?, ?, ?, ?> theValidationSupport) {
myValidationSupport = theValidationSupport;
}
@SuppressWarnings({ "cast" })
@SuppressWarnings({"cast"})
private List<Class<? extends IElement>> toElementList(Collection<Class<? extends IBaseResource>> theResourceTypes) {
if (theResourceTypes == null) {
return null;
@ -858,13 +888,6 @@ public class FhirContext {
return new FhirContext(FhirVersionEnum.DSTU2);
}
/**
* Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2 DSTU2} (2016 May DSTU3 Snapshot)
*/
public static FhirContext forDstu2_1() {
return new FhirContext(FhirVersionEnum.DSTU2_1);
}
/**
* Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2_HL7ORG DSTU2} (using the Reference
* Implementation Structures)
@ -873,6 +896,13 @@ public class FhirContext {
return new FhirContext(FhirVersionEnum.DSTU2_HL7ORG);
}
/**
* Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2 DSTU2} (2016 May DSTU3 Snapshot)
*/
public static FhirContext forDstu2_1() {
return new FhirContext(FhirVersionEnum.DSTU2_1);
}
/**
* Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU3 DSTU3}
*
@ -891,7 +921,6 @@ public class FhirContext {
return new FhirContext(FhirVersionEnum.R4);
}
private static Collection<Class<? extends IBaseResource>> toCollection(Class<? extends IBaseResource> theResourceType) {
ArrayList<Class<? extends IBaseResource>> retVal = new ArrayList<Class<? extends IBaseResource>>(1);
retVal.add(theResourceType);
@ -909,34 +938,4 @@ public class FhirContext {
}
return retVal;
}
/**
* Returns an unmodifiable set containing all resource names known to this
* context
*/
public Set<String> getResourceNames() {
Set<String> resourceNames= new HashSet<>();
if (myNameToResourceDefinition.isEmpty()) {
Properties props = new Properties();
try {
props.load(myVersion.getFhirVersionPropertiesFile());
} catch (IOException theE) {
throw new ConfigurationException("Failed to load version properties file");
}
Enumeration<?> propNames = props.propertyNames();
while (propNames.hasMoreElements()){
String next = (String) propNames.nextElement();
if (next.startsWith("resource.")) {
resourceNames.add(next.substring("resource.".length()).trim());
}
}
}
for (RuntimeResourceDefinition next : myNameToResourceDefinition.values()) {
resourceNames.add(next.getName());
}
return Collections.unmodifiableSet(resourceNames);
}
}

View File

@ -34,8 +34,17 @@ public class UrlPathTokenizer {
return myTok.hasMoreTokens();
}
public String nextToken() {
return UrlUtil.unescape(myTok.nextToken());
/**
* Returns the next portion. Any URL-encoding is undone, but we will
* HTML encode the &lt; and &quot; marks since they are both
* not useful un URL paths in FHIR and potentially represent injection
* attacks.
*
* @see UrlUtil#sanitizeUrlPart(String)
* @see UrlUtil#unescape(String)
*/
public String nextTokenUnescapedAndSanitized() {
return UrlUtil.sanitizeUrlPart(UrlUtil.unescape(myTok.nextToken()));
}
}

View File

@ -70,7 +70,7 @@ public class UrlUtil {
return theExtensionUrl;
}
if (theExtensionUrl == null) {
return theExtensionUrl;
return null;
}
int parentLastSlashIdx = theParentExtensionUrl.lastIndexOf('/');
@ -119,6 +119,18 @@ public class UrlUtil {
return value.startsWith("http://") || value.startsWith("https://");
}
public static boolean isNeedsSanitization(String theString) {
if (theString != null) {
for (int i = 0; i < theString.length(); i++) {
char nextChar = theString.charAt(i);
if (nextChar == '<' || nextChar == '"') {
return true;
}
}
}
return false;
}
public static boolean isValid(String theUrl) {
if (theUrl == null || theUrl.length() < 8) {
return false;
@ -164,7 +176,7 @@ public class UrlUtil {
}
public static Map<String, String[]> parseQueryString(String theQueryString) {
HashMap<String, List<String>> map = new HashMap<String, List<String>>();
HashMap<String, List<String>> map = new HashMap<>();
parseQueryString(theQueryString, map);
return toQueryStringMap(map);
}
@ -197,17 +209,13 @@ public class UrlUtil {
nextKey = unescape(nextKey);
nextValue = unescape(nextValue);
List<String> list = map.get(nextKey);
if (list == null) {
list = new ArrayList<>();
map.put(nextKey, list);
}
List<String> list = map.computeIfAbsent(nextKey, k -> new ArrayList<>());
list.add(nextValue);
}
}
public static Map<String, String[]> parseQueryStrings(String... theQueryString) {
HashMap<String, List<String>> map = new HashMap<String, List<String>>();
HashMap<String, List<String>> map = new HashMap<>();
for (String next : theQueryString) {
parseQueryString(next, map);
}
@ -222,7 +230,6 @@ public class UrlUtil {
* <li>[Resource Type]/[Resource ID]/_history/[Version ID]
* </ul>
*/
//@formatter:on
public static UrlParts parseUrl(String theUrl) {
String url = theUrl;
UrlParts retVal = new UrlParts();
@ -243,7 +250,7 @@ public class UrlUtil {
retVal.setVersionId(id.getVersionIdPart());
return retVal;
}
if (url.matches("\\/[a-zA-Z]+\\?.*")) {
if (url.matches("/[a-zA-Z]+\\?.*")) {
url = url.substring(1);
}
int nextStart = 0;
@ -282,12 +289,47 @@ public class UrlUtil {
}
//@formatter:off
/**
* This method specifically HTML-encodes the &quot; and
* &lt; characters in order to prevent injection attacks
*/
public static String sanitizeUrlPart(String theString) {
if (theString == null) {
return null;
}
boolean needsSanitization = isNeedsSanitization(theString);
if (needsSanitization) {
// Ok, we're sanitizing
StringBuilder buffer = new StringBuilder(theString.length() + 10);
for (int j = 0; j < theString.length(); j++) {
char nextChar = theString.charAt(j);
switch (nextChar) {
case '"':
buffer.append("&quot;");
break;
case '<':
buffer.append("&lt;");
break;
default:
buffer.append(nextChar);
break;
}
} // for build escaped string
return buffer.toString();
}
return theString;
}
private static Map<String, String[]> toQueryStringMap(HashMap<String, List<String>> map) {
HashMap<String, String[]> retVal = new HashMap<String, String[]>();
HashMap<String, String[]> retVal = new HashMap<>();
for (Entry<String, List<String>> nextEntry : map.entrySet()) {
retVal.put(nextEntry.getKey(), nextEntry.getValue().toArray(new String[nextEntry.getValue().size()]));
retVal.put(nextEntry.getKey(), nextEntry.getValue().toArray(new String[0]));
}
return retVal;
}

View File

@ -31,6 +31,13 @@
<appender-ref ref="STDOUT" />
</logger>
<!--
It's useful to have this log when uploading big terminologies
-->
<logger name="ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<root level="warn">
<appender-ref ref="STDOUT" />

View File

@ -1103,7 +1103,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
@SuppressWarnings("unchecked")
@Override
public Object execute() {
if (myOperationName != null && myOperationName.equals(Constants.EXTOP_PROCESS_MESSAGE)) {
if (myOperationName != null && myOperationName.equals(Constants.EXTOP_PROCESS_MESSAGE) && myMsgBundle != null) {
Map<String, List<String>> urlParams = new LinkedHashMap<String, List<String>>();
// Set Url parameter Async and Response-Url
if (myIsAsync != null) {

View File

@ -57,17 +57,6 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca
private IIdType myForceResourceId;
public BaseHttpClientInvocationWithContents(FhirContext theContext, IBaseResource theResource, Map<String, List<String>> theParams, String... theUrlPath) {
super(theContext);
myResource = theResource;
myUrlPath = StringUtils.join(theUrlPath, '/');
myResources = null;
myContents = null;
myParams = theParams;
myBundleType = null;
}
public BaseHttpClientInvocationWithContents(FhirContext theContext, IBaseResource theResource, String theUrlPath) {
super(theContext);
myResource = theResource;
@ -105,17 +94,6 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca
myBundleType = null;
}
public BaseHttpClientInvocationWithContents(FhirContext theContext, String theContents, Map<String, List<String>> theParams, String... theUrlPath) {
super(theContext);
myResource = null;
myUrlPath = StringUtils.join(theUrlPath, '/');
myResources = null;
myContents = theContents;
myParams = theParams;
myBundleType = null;
}
@Override
public IHttpRequest asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) throws DataFormatException {
StringBuilder url = new StringBuilder();

View File

@ -1301,20 +1301,24 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
}
}
// Don't keep duplicate tags
Set<TagDefinition> allDefsPresent = new HashSet<>();
theEntity.getTags().removeIf(theResourceTag -> !allDefsPresent.add(theResourceTag.getTag()));
// Remove any tags that have been removed
for (ResourceTag next : allTagsOld) {
if (!allDefs.contains(next)) {
if (shouldDroppedTagBeRemovedOnUpdate(theRequest, next)) {
theEntity.getTags().remove(next);
}
}
}
Set<ResourceTag> allTagsNew = getAllTagDefinitions(theEntity);
Set<TagDefinition> allDefsPresent = new HashSet<>();
allTagsNew.forEach(tag -> {
// Don't keep duplicate tags
if (!allDefsPresent.add(tag.getTag())) {
theEntity.getTags().remove(tag);
}
// Drop any tags that have been removed
if (!allDefs.contains(tag)) {
if (shouldDroppedTagBeRemovedOnUpdate(theRequest, tag)) {
theEntity.getTags().remove(tag);
}
}
});
if (!allTagsOld.equals(allTagsNew)) {
changed = true;
}
@ -1473,6 +1477,15 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
return retVal;
}
/**
* Subclasses may override to provide behaviour. Called when a pre-existing resource has been updated in the database
*
* @param theEntity The resource
*/
protected void postDelete(ResourceTable theEntity) {
// nothing
}
/**
* Subclasses may override to provide behaviour. Called when a resource has been inserted into the database for the first time.
*
@ -1483,15 +1496,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
// nothing
}
/**
* Subclasses may override to provide behaviour. Called when a pre-existing resource has been updated in the database
*
* @param theEntity The resource
*/
protected void postDelete(ResourceTable theEntity) {
// nothing
}
/**
* Subclasses may override to provide behaviour. Called when a pre-existing resource has been updated in the database
*
@ -1638,7 +1642,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
resourceEncoding = history.getEncoding();
myTagList = history.getTags();
} else if (theEntity instanceof ResourceTable) {
ResourceTable resource = (ResourceTable)theEntity;
ResourceTable resource = (ResourceTable) theEntity;
ResourceHistoryTable history = myResourceHistoryTableDao.findForIdAndVersion(theEntity.getId(), theEntity.getVersion());
if (history == null) {
return null;
@ -1648,7 +1652,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
myTagList = resource.getTags();
} else if (theEntity instanceof ResourceSearchView) {
// This is the search View
ResourceSearchView myView = (ResourceSearchView)theEntity;
ResourceSearchView myView = (ResourceSearchView) theEntity;
resourceBytes = myView.getResource();
resourceEncoding = myView.getEncoding();
if (theTagList == null)
@ -2046,6 +2050,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
postPersist(theEntity, (T) theResource);
} else if (theEntity.getDeleted() != null) {
theEntity = myEntityManager.merge(theEntity);
postDelete(theEntity);
@ -2060,10 +2065,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
*/
if (theCreateNewHistoryEntry) {
final ResourceHistoryTable historyEntry = theEntity.toHistory();
// if (theEntity.getVersion() > 1) {
// existing = myResourceHistoryTableDao.findForIdAndVersion(theEntity.getId(), theEntity.getVersion());
// ourLog.warn("Reusing existing history entry entity {}", theEntity.getIdDt().getValue());
// }
historyEntry.setEncoding(changed.getEncoding());
historyEntry.setResource(changed.getResource());
@ -2195,12 +2196,11 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
} // if thePerformIndexing
theEntity = myEntityManager.merge(theEntity);
if (theResource != null) {
populateResourceIdFromEntity(theEntity, theResource);
}
return theEntity;
}

View File

@ -395,16 +395,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
"This server cannot create an entity with a user-specified numeric ID - Client should not specify an ID when creating a new resource, or should include at least one letter in the ID to force a client-defined ID");
}
createForcedIdIfNeeded(entity, theResource.getIdElement());
if (entity.getForcedId() != null) {
try {
translateForcedIdToPid(getResourceName(), theResource.getIdElement().getIdPart());
throw new UnprocessableEntityException(getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "duplicateCreateForcedId", theResource.getIdElement().getIdPart()));
} catch (ResourceNotFoundException e) {
// good, this ID doesn't exist so we can create it
}
}
}
// Notify interceptors
@ -1211,7 +1201,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
} else {
/*
* Note: resourcdeId will not be null or empty here, because we check it and reject requests in BaseOutcomeReturningMethodBindingWithResourceParam
* Note: resourceId will not be null or empty here, because we
* check it and reject requests in
* BaseOutcomeReturningMethodBindingWithResourceParam
*/
resourceId = theResource.getIdElement();

View File

@ -0,0 +1,38 @@
package ca.uhn.fhir.jpa.dao;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.model.dstu2.resource.MessageHeader;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
import org.hl7.fhir.instance.model.api.IBaseBundle;
public class FhirResourceDaoMessageHeaderDstu2 extends FhirResourceDaoDstu2<MessageHeader> implements IFhirResourceDaoMessageHeader<MessageHeader> {
@Override
public IBaseBundle messageHeaderProcessMessage(RequestDetails theRequestDetails, IBaseBundle theMessage) {
return FhirResourceDaoMessageHeaderDstu2.throwProcessMessageNotImplemented();
}
public static IBaseBundle throwProcessMessageNotImplemented() {
throw new NotImplementedOperationException("This operation is not yet implemented on this server");
}
}

View File

@ -0,0 +1,31 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public interface IFhirResourceDaoMessageHeader<T extends IBaseResource> extends IFhirResourceDao<T> {
IBaseBundle messageHeaderProcessMessage(RequestDetails theRequestDetails, IBaseBundle theMessage);
}

View File

@ -34,15 +34,18 @@ import java.util.List;
public interface ITermConceptDao extends JpaRepository<TermConcept, Long> {
@Query("SELECT COUNT(t) FROM TermConcept t WHERE t.myCodeSystem.myId = :cs_pid")
Integer countByCodeSystemVersion(@Param("cs_pid") Long thePid);
@Query("SELECT c FROM TermConcept c WHERE c.myCodeSystem = :code_system AND c.myCode = :code")
TermConcept findByCodeSystemAndCode(@Param("code_system") TermCodeSystemVersion theCodeSystem, @Param("code") String theCode);
@Query("SELECT c FROM TermConcept c WHERE c.myCodeSystem = :code_system")
List<TermConcept> findByCodeSystemVersion(@Param("code_system") TermCodeSystemVersion theCodeSystem);
@Query("SELECT t FROM TermConcept t WHERE t.myCodeSystem.myId = :cs_pid")
Slice<TermConcept> findByCodeSystemVersion(Pageable thePage, @Param("cs_pid") Long thePid);
@Query("SELECT c FROM TermConcept c WHERE c.myCodeSystem = :code_system")
List<TermConcept> findByCodeSystemVersion(@Param("code_system") TermCodeSystemVersion theCodeSystem);
@Query("SELECT t FROM TermConcept t WHERE t.myIndexStatus = null")
Page<TermConcept> findResourcesRequiringReindexing(Pageable thePageRequest);

View File

@ -32,4 +32,7 @@ public interface ITermConceptDesignationDao extends JpaRepository<TermConceptDes
@Query("SELECT t FROM TermConceptDesignation t WHERE t.myCodeSystemVersion.myId = :csv_pid")
Slice<TermConceptDesignation> findByCodeSystemVersion(Pageable thePage, @Param("csv_pid") Long thePid);
@Query("SELECT COUNT(t) FROM TermConceptDesignation t WHERE t.myCodeSystemVersion.myId = :csv_pid")
Integer countByCodeSystemVersion(@Param("csv_pid") Long thePid);
}

View File

@ -31,6 +31,9 @@ import java.util.Collection;
public interface ITermConceptParentChildLinkDao extends JpaRepository<TermConceptParentChildLink, Long> {
@Query("SELECT COUNT(t) FROM TermConceptParentChildLink t WHERE t.myCodeSystem.myId = :cs_pid")
Integer countByCodeSystemVersion(@Param("cs_pid") Long thePid);
@Query("SELECT t.myParentPid FROM TermConceptParentChildLink t WHERE t.myChildPid = :child_pid")
Collection<Long> findAllWithChild(@Param("child_pid") Long theConceptPid);

View File

@ -32,4 +32,6 @@ public interface ITermConceptPropertyDao extends JpaRepository<TermConceptProper
@Query("SELECT t FROM TermConceptProperty t WHERE t.myCodeSystemVersion.myId = :cs_pid")
Slice<TermConceptProperty> findByCodeSystemVersion(Pageable thePage, @Param("cs_pid") Long thePid);
@Query("SELECT COUNT(t) FROM TermConceptProperty t WHERE t.myCodeSystemVersion.myId = :cs_pid")
Integer countByCodeSystemVersion(@Param("cs_pid") Long thePid);
}

View File

@ -0,0 +1,36 @@
package ca.uhn.fhir.jpa.dao.dstu3;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.dao.FhirResourceDaoMessageHeaderDstu2;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoMessageHeader;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.dstu3.model.MessageHeader;
import org.hl7.fhir.instance.model.api.IBaseBundle;
public class FhirResourceDaoMessageHeaderDstu3 extends FhirResourceDaoDstu3<MessageHeader> implements IFhirResourceDaoMessageHeader<MessageHeader> {
@Override
public IBaseBundle messageHeaderProcessMessage(RequestDetails theRequestDetails, IBaseBundle theMessage) {
return FhirResourceDaoMessageHeaderDstu2.throwProcessMessageNotImplemented();
}
}

View File

@ -25,6 +25,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.codec.binary.StringUtils;
import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext;
@ -35,6 +36,8 @@ import org.hl7.fhir.dstu3.model.ValueSet.*;
import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@ -223,6 +226,7 @@ public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3<ValueSet>
if (vs != null) {
ValueSet expansion = doExpand(vs);
List<ValueSetExpansionContainsComponent> contains = expansion.getExpansion().getContains();
ValidateCodeResult result = validateCodeIsInContains(contains, toStringOrNull(theSystem), toStringOrNull(theCode), theCoding, theCodeableConcept);
if (result != null) {
if (theDisplay != null && isNotBlank(theDisplay.getValue()) && isNotBlank(result.getDisplay())) {
@ -238,6 +242,9 @@ public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3<ValueSet>
}
private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoValueSetDstu3.class);
private String toStringOrNull(IPrimitiveType<String> thePrimitive) {
return thePrimitive != null ? thePrimitive.getValue() : null;
}

View File

@ -0,0 +1,36 @@
package ca.uhn.fhir.jpa.dao.r4;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.dao.FhirResourceDaoMessageHeaderDstu2;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoMessageHeader;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.r4.model.MessageHeader;
public class FhirResourceDaoMessageHeaderR4 extends FhirResourceDaoR4<MessageHeader> implements IFhirResourceDaoMessageHeader<MessageHeader> {
@Override
public IBaseBundle messageHeaderProcessMessage(RequestDetails theRequestDetails, IBaseBundle theMessage) {
return FhirResourceDaoMessageHeaderDstu2.throwProcessMessageNotImplemented();
}
}

View File

@ -187,7 +187,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
*/
@Override
public HashSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamNumber> retVal = new HashSet<ResourceIndexedSearchParamNumber>();
HashSet<ResourceIndexedSearchParamNumber> retVal = new HashSet<>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
@ -290,7 +290,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
*/
@Override
public Set<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamQuantity> retVal = new HashSet<ResourceIndexedSearchParamQuantity>();
HashSet<ResourceIndexedSearchParamQuantity> retVal = new HashSet<>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
@ -354,7 +354,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
*/
@Override
public Set<ResourceIndexedSearchParamString> extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamString> retVal = new HashSet<ResourceIndexedSearchParamString>();
HashSet<ResourceIndexedSearchParamString> retVal = new HashSet<>();
String resourceName = getContext().getResourceDefinition(theResource).getName();
@ -397,7 +397,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
addSearchTerm(theEntity, retVal, nextSpName, searchTerm);
} else {
if (nextObject instanceof HumanName) {
ArrayList<StringType> allNames = new ArrayList<StringType>();
ArrayList<StringType> allNames = new ArrayList<>();
HumanName nextHumanName = (HumanName) nextObject;
if (isNotBlank(nextHumanName.getFamily())) {
allNames.add(nextHumanName.getFamilyElement());
@ -407,7 +407,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue());
}
} else if (nextObject instanceof Address) {
ArrayList<StringType> allNames = new ArrayList<StringType>();
ArrayList<StringType> allNames = new ArrayList<>();
Address nextAddress = (Address) nextObject;
allNames.addAll(nextAddress.getLine());
allNames.add(nextAddress.getCityElement());
@ -573,7 +573,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
assert systems.size() == codes.size() : "Systems contains " + systems + ", codes contains: " + codes;
Set<Pair<String, String>> haveValues = new HashSet<Pair<String, String>>();
Set<Pair<String, String>> haveValues = new HashSet<>();
for (int i = 0; i < systems.size(); i++) {
String system = systems.get(i);
String code = codes.get(i);
@ -608,7 +608,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
@Override
public Set<ResourceIndexedSearchParamUri> extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamUri> retVal = new HashSet<ResourceIndexedSearchParamUri>();
HashSet<ResourceIndexedSearchParamUri> retVal = new HashSet<>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
@ -690,7 +690,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
IWorkerContext worker = new org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext(getContext(), myValidationSupport);
FHIRPathEngine fp = new FHIRPathEngine(worker);
List<Object> values = new ArrayList<Object>();
List<Object> values = new ArrayList<>();
try {
String[] nextPathsSplit = SPLIT.split(thePaths);
for (String nextPath : nextPathsSplit) {
@ -717,7 +717,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
@Override
public List<PathAndRef> extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) {
ArrayList<PathAndRef> retVal = new ArrayList<PathAndRef>();
ArrayList<PathAndRef> retVal = new ArrayList<>();
String[] nextPathsSplit = SPLIT.split(theNextSpDef.getPath());
for (String path : nextPathsSplit) {

View File

@ -63,6 +63,7 @@ public abstract class BaseHasResource implements IBaseResourceEntity {
public abstract BaseTag addTag(TagDefinition theDef);
@Override
public Date getDeleted() {
return myDeleted;
}
@ -72,6 +73,7 @@ public abstract class BaseHasResource implements IBaseResourceEntity {
}
@Override
public FhirVersionEnum getFhirVersion() {
return myFhirVersion;
}
@ -88,10 +90,13 @@ public abstract class BaseHasResource implements IBaseResourceEntity {
myForcedId = theForcedId;
}
@Override
public abstract Long getId();
@Override
public abstract IdDt getIdDt();
@Override
public InstantDt getPublished() {
if (myPublished != null) {
return new InstantDt(myPublished);
@ -104,12 +109,15 @@ public abstract class BaseHasResource implements IBaseResourceEntity {
myPublished = thePublished;
}
@Override
public abstract Long getResourceId();
@Override
public abstract String getResourceType();
public abstract Collection<? extends BaseTag> getTags();
@Override
public InstantDt getUpdated() {
return new InstantDt(myUpdated);
}
@ -118,12 +126,15 @@ public abstract class BaseHasResource implements IBaseResourceEntity {
myUpdated = theUpdated;
}
@Override
public Date getUpdatedDate() {
return myUpdated;
}
@Override
public abstract long getVersion();
@Override
public boolean isHasTags() {
return myHasTags;
}

View File

@ -24,16 +24,18 @@ import org.hibernate.annotations.ColumnDefault;
import javax.persistence.*;
//@formatter:off
@Entity()
@Table(name = "HFJ_FORCED_ID", uniqueConstraints = {
@UniqueConstraint(name = "IDX_FORCEDID_RESID", columnNames = {"RESOURCE_PID"}),
@UniqueConstraint(name = "IDX_FORCEDID_TYPE_RESID", columnNames = {"RESOURCE_TYPE", "RESOURCE_PID"}),
@UniqueConstraint(name = "IDX_FORCEDID_TYPE_FID", columnNames = {"RESOURCE_TYPE", "FORCED_ID"})
}, indexes = {
@Index(name = "IDX_FORCEDID_TYPE_FORCEDID", columnList = "RESOURCE_TYPE,FORCED_ID"),
/*
* NB: We previously had indexes named
* - IDX_FORCEDID_TYPE_FORCEDID
* - IDX_FORCEDID_TYPE_RESID
* so don't reuse these names
*/
})
//@formatter:on
public class ForcedId {
public static final int MAX_FORCED_ID_LENGTH = 100;

View File

@ -62,7 +62,8 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
public String mySystem;
@Field()
@Column(name = "SP_VALUE", nullable = true, length = MAX_LENGTH)
public String myValue;
private String myValue;
@SuppressWarnings("unused")
@Id
@SequenceGenerator(name = "SEQ_SPIDX_TOKEN", sequenceName = "SEQ_SPIDX_TOKEN")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_TOKEN")
@ -152,40 +153,40 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
return b.isEquals();
}
public Long getHashSystem() {
Long getHashSystem() {
calculateHashes();
return myHashSystem;
}
public Long getHashIdentity() {
private Long getHashIdentity() {
calculateHashes();
return myHashIdentity;
}
public void setHashIdentity(Long theHashIdentity) {
private void setHashIdentity(Long theHashIdentity) {
myHashIdentity = theHashIdentity;
}
public void setHashSystem(Long theHashSystem) {
private void setHashSystem(Long theHashSystem) {
myHashSystem = theHashSystem;
}
public Long getHashSystemAndValue() {
Long getHashSystemAndValue() {
calculateHashes();
return myHashSystemAndValue;
}
public void setHashSystemAndValue(Long theHashSystemAndValue) {
private void setHashSystemAndValue(Long theHashSystemAndValue) {
calculateHashes();
myHashSystemAndValue = theHashSystemAndValue;
}
public Long getHashValue() {
Long getHashValue() {
calculateHashes();
return myHashValue;
}
public void setHashValue(Long theHashValue) {
private void setHashValue(Long theHashValue) {
myHashValue = theHashValue;
}

View File

@ -45,7 +45,8 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
@Table(name = "TRM_CONCEPT", uniqueConstraints = {
@UniqueConstraint(name = "IDX_CONCEPT_CS_CODE", columnNames = {"CODESYSTEM_PID", "CODE"})
}, indexes = {
@Index(name = "IDX_CONCEPT_INDEXSTATUS", columnList = "INDEX_STATUS")
@Index(name = "IDX_CONCEPT_INDEXSTATUS", columnList = "INDEX_STATUS"),
@Index(name = "IDX_CONCEPT_UPDATED", columnList = "CONCEPT_UPDATED")
})
public class TermConcept implements Serializable {
protected static final int MAX_DESC_LENGTH = 400;
@ -59,15 +60,15 @@ public class TermConcept implements Serializable {
@Column(name = "CODE", length = 100, nullable = false)
@Fields({@Field(name = "myCode", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "exactAnalyzer")),})
private String myCode;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "CONCEPT_UPDATED", nullable = true)
private Date myUpdated;
@ManyToOne()
@JoinColumn(name = "CODESYSTEM_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPT_PID_CS_PID"))
private TermCodeSystemVersion myCodeSystem;
@Column(name = "CODESYSTEM_PID", insertable = false, updatable = false)
@Fields({@Field(name = "myCodeSystemVersionPid")})
private long myCodeSystemVersionPid;
@Column(name = "DISPLAY", length = MAX_DESC_LENGTH, nullable = true)
@Fields({
@Field(name = "myDisplay", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "standardAnalyzer")),
@ -76,15 +77,12 @@ public class TermConcept implements Serializable {
@Field(name = "myDisplayPhonetic", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompletePhoneticAnalyzer"))
})
private String myDisplay;
@OneToMany(mappedBy = "myConcept", orphanRemoval = false)
@Field(name = "PROPmyProperties", analyzer = @Analyzer(definition = "termConceptPropertyAnalyzer"))
@FieldBridge(impl = TermConceptPropertyFieldBridge.class)
private Collection<TermConceptProperty> myProperties;
@OneToMany(mappedBy = "myConcept", orphanRemoval = false)
private Collection<TermConceptDesignation> myDesignations;
@Id()
@SequenceGenerator(name = "SEQ_CONCEPT_PID", sequenceName = "SEQ_CONCEPT_PID")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_PID")
@ -92,18 +90,17 @@ public class TermConcept implements Serializable {
private Long myId;
@Column(name = "INDEX_STATUS", nullable = true)
private Long myIndexStatus;
@Transient
@Field(name = "myParentPids", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "conceptParentPidsAnalyzer"))
@Lob
@Column(name="PARENT_PIDS", nullable = true)
private String myParentPids;
@OneToMany(cascade = {}, fetch = FetchType.LAZY, mappedBy = "myChild")
private Collection<TermConceptParentChildLink> myParents;
@Column(name = "CODE_SEQUENCE", nullable = true)
private Integer mySequence;
public TermConcept() {
super();
}
public TermConcept(TermCodeSystemVersion theCs, String theCode) {
setCodeSystemVersion(theCs);
setCode(theCode);
@ -296,6 +293,14 @@ public class TermConcept implements Serializable {
return null;
}
public Date getUpdated() {
return myUpdated;
}
public void setUpdated(Date theUpdated) {
myUpdated = theUpdated;
}
@Override
public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder();

View File

@ -0,0 +1,61 @@
package ca.uhn.fhir.jpa.provider;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoMessageHeader;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.MessageHeader;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import javax.servlet.http.HttpServletRequest;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public class BaseJpaResourceProviderMessageHeaderDstu2 extends JpaResourceProviderDstu2<MessageHeader> {
/**
* /MessageHeader/$process-message
*/
@Operation(name = JpaConstants.OPERATION_PROCESS_MESSAGE, idempotent = false)
public IBaseBundle processMessage(
HttpServletRequest theServletRequest,
RequestDetails theRequestDetails,
@OperationParam(name = "content", min = 1, max = 1)
@Description(formalDefinition = "The message to process (or, if using asynchronous messaging, it may be a response message to accept)")
Bundle theMessageToProcess
) {
startRequest(theServletRequest);
try {
return ((IFhirResourceDaoMessageHeader<MessageHeader>) getDao()).messageHeaderProcessMessage(theRequestDetails, theMessageToProcess);
} finally {
endRequest(theServletRequest);
}
}
}

View File

@ -0,0 +1,61 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoMessageHeader;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.MessageHeader;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import javax.servlet.http.HttpServletRequest;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public class BaseJpaResourceProviderMessageHeaderDstu3 extends JpaResourceProviderDstu3<MessageHeader> {
/**
* /MessageHeader/$process-message
*/
@Operation(name = JpaConstants.OPERATION_PROCESS_MESSAGE, idempotent = false)
public IBaseBundle processMessage(
HttpServletRequest theServletRequest,
RequestDetails theRequestDetails,
@OperationParam(name = "content", min = 1, max = 1)
@Description(formalDefinition = "The message to process (or, if using asynchronous messaging, it may be a response message to accept)")
Bundle theMessageToProcess
) {
startRequest(theServletRequest);
try {
return ((IFhirResourceDaoMessageHeader<MessageHeader>) getDao()).messageHeaderProcessMessage(theRequestDetails, theMessageToProcess);
} finally {
endRequest(theServletRequest);
}
}
}

View File

@ -0,0 +1,61 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoMessageHeader;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.MessageHeader;
import javax.servlet.http.HttpServletRequest;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public class BaseJpaResourceProviderMessageHeaderR4 extends JpaResourceProviderR4<MessageHeader> {
/**
* /MessageHeader/$process-message
*/
@Operation(name = JpaConstants.OPERATION_PROCESS_MESSAGE, idempotent = false)
public IBaseBundle processMessage(
HttpServletRequest theServletRequest,
RequestDetails theRequestDetails,
@OperationParam(name = "content", min = 1, max = 1)
@Description(formalDefinition = "The message to process (or, if using asynchronous messaging, it may be a response message to accept)")
Bundle theMessageToProcess
) {
startRequest(theServletRequest);
try {
return ((IFhirResourceDaoMessageHeader<MessageHeader>) getDao()).messageHeaderProcessMessage(theRequestDetails, theMessageToProcess);
} finally {
endRequest(theServletRequest);
}
}
}

View File

@ -20,9 +20,7 @@ package ca.uhn.fhir.jpa.search;
* #L%
*/
import org.apache.lucene.analysis.core.LowerCaseFilterFactory;
import org.apache.lucene.analysis.core.StopFilterFactory;
import org.apache.lucene.analysis.core.WhitespaceTokenizerFactory;
import org.apache.lucene.analysis.core.*;
import org.apache.lucene.analysis.miscellaneous.WordDelimiterFilterFactory;
import org.apache.lucene.analysis.ngram.EdgeNGramFilterFactory;
import org.apache.lucene.analysis.ngram.NGramFilterFactory;
@ -65,7 +63,7 @@ public class LuceneSearchMappingFactory {
.param("maxGramSize", "20")
.analyzerDef("standardAnalyzer", StandardTokenizerFactory.class)
.filter(LowerCaseFilterFactory.class)
.analyzerDef("exactAnalyzer", StandardTokenizerFactory.class)
.analyzerDef("exactAnalyzer", KeywordTokenizerFactory.class)
.analyzerDef("conceptParentPidsAnalyzer", WhitespaceTokenizerFactory.class)
.analyzerDef("termConceptPropertyAnalyzer", WhitespaceTokenizerFactory.class);

View File

@ -45,14 +45,15 @@ import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.lucene.index.Term;
import org.apache.lucene.queries.TermsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.RegexpQuery;
import org.apache.lucene.search.*;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.query.dsl.BooleanJunction;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.hibernate.search.query.dsl.TermMatchingContext;
import org.hibernate.search.query.dsl.TermTermination;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.CodeSystem;
@ -66,6 +67,7 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
@ -82,6 +84,7 @@ import javax.persistence.TypedQuery;
import javax.persistence.criteria.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank;
@ -273,74 +276,43 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
myEntityManager.flush();
}
public void deleteCodeSystemVersion(Long theCodeSystemVersionPid) {
public void deleteCodeSystemVersion(final Long theCodeSystemVersionPid) {
ourLog.info(" * Deleting code system version {}", theCodeSystemVersionPid);
PageRequest page = PageRequest.of(0, 1000);
int count;
PageRequest page1000 = PageRequest.of(0, 1000);
// Parent/Child links
ourLog.info(" * Deleting parent/child links");
count = 0;
while (true) {
Slice<TermConceptParentChildLink> link = myConceptParentChildLinkDao.findByCodeSystemVersion(page, theCodeSystemVersionPid);
if (link.hasContent() == false) {
break;
{
String descriptor = "parent/child links";
Supplier<Slice<TermConceptParentChildLink>> loader = () -> myConceptParentChildLinkDao.findByCodeSystemVersion(page1000, theCodeSystemVersionPid);
Supplier<Integer> counter = () -> myConceptParentChildLinkDao.countByCodeSystemVersion(theCodeSystemVersionPid);
doDelete(descriptor, loader, counter, myConceptParentChildLinkDao);
}
myConceptParentChildLinkDao.deleteInBatch(link);
count += link.getNumberOfElements();
ourLog.info(" * {} parent/child links deleted", count);
}
myConceptParentChildLinkDao.flush();
// Properties
ourLog.info(" * Deleting properties");
count = 0;
while (true) {
Slice<TermConceptProperty> link = myConceptPropertyDao.findByCodeSystemVersion(page, theCodeSystemVersionPid);
if (link.hasContent() == false) {
break;
{
String descriptor = "concept properties";
Supplier<Slice<TermConceptProperty>> loader = () -> myConceptPropertyDao.findByCodeSystemVersion(page1000, theCodeSystemVersionPid);
Supplier<Integer> counter = () -> myConceptPropertyDao.countByCodeSystemVersion(theCodeSystemVersionPid);
doDelete(descriptor, loader, counter, myConceptPropertyDao);
}
myConceptPropertyDao.deleteInBatch(link);
count += link.getNumberOfElements();
ourLog.info(" * {} concept properties deleted", count);
// Designations
{
String descriptor = "concept designations";
Supplier<Slice<TermConceptDesignation>> loader = () -> myConceptDesignationDao.findByCodeSystemVersion(page1000, theCodeSystemVersionPid);
Supplier<Integer> counter = () -> myConceptDesignationDao.countByCodeSystemVersion(theCodeSystemVersionPid);
doDelete(descriptor, loader, counter, myConceptDesignationDao);
}
myConceptPropertyDao.flush();
// Properties
ourLog.info(" * Deleting designations");
count = 0;
while (true) {
Slice<TermConceptDesignation> link = myConceptDesignationDao.findByCodeSystemVersion(page, theCodeSystemVersionPid);
if (link.hasContent() == false) {
break;
}
myConceptDesignationDao.deleteInBatch(link);
count += link.getNumberOfElements();
ourLog.info(" * {} concept designations deleted", count);
}
myConceptDesignationDao.flush();
// Concepts
ourLog.info(" * Deleting concepts");
count = 0;
while (true) {
Slice<TermConcept> link = myConceptDao.findByCodeSystemVersion(page, theCodeSystemVersionPid);
if (link.hasContent() == false) {
break;
}
myConceptDao.deleteInBatch(link);
myConceptDao.flush();
count += link.getNumberOfElements();
ourLog.info(" * {} concepts deleted", count);
{
String descriptor = "concepts";
// For some reason, concepts are much slower to delete, so use a smaller batch size
PageRequest page100 = PageRequest.of(0, 100);
Supplier<Slice<TermConcept>> loader = () -> myConceptDao.findByCodeSystemVersion(page100, theCodeSystemVersionPid);
Supplier<Integer> counter = () -> myConceptDao.countByCodeSystemVersion(theCodeSystemVersionPid);
doDelete(descriptor, loader, counter, myConceptDao);
}
Optional<TermCodeSystem> codeSystemOpt = myCodeSystemDao.findWithCodeSystemVersionAsCurrentVersion(theCodeSystemVersionPid);
@ -397,6 +369,26 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
deleteConceptMap(theResourceTable);
}
private <T> void doDelete(String theDescriptor, Supplier<Slice<T>> theLoader, Supplier<Integer> theCounter, JpaRepository<T, ?> theDao) {
int count;
ourLog.info(" * Deleting {}", theDescriptor);
int totalCount = theCounter.get();
StopWatch sw = new StopWatch();
count = 0;
while (true) {
Slice<T> link = theLoader.get();
if (link.hasContent() == false) {
break;
}
theDao.deleteInBatch(link);
count += link.getNumberOfElements();
ourLog.info(" * {} {} deleted - {}/sec - ETA: {}", count, theDescriptor, sw.formatThroughput(count, TimeUnit.SECONDS), sw.getEstimatedTimeRemaining(count, totalCount));
}
theDao.flush();
}
private int ensureParentsSaved(Collection<TermConceptParentChildLink> theParents) {
ourLog.trace("Checking {} parents", theParents.size());
int retVal = 0;
@ -406,6 +398,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
TermConcept nextParent = nextLink.getParent();
retVal += ensureParentsSaved(nextParent.getParents());
if (nextParent.getId() == null) {
nextParent.setUpdated(new Date());
myConceptDao.saveAndFlush(nextParent);
retVal++;
ourLog.debug("Saved parent code {} and got id {}", nextParent.getCode(), nextParent.getId());
@ -466,21 +459,6 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
bool.must(qb.keyword().onField("myCodeSystemVersionPid").matching(csv.getPid()).createQuery());
/*
* Include Concepts
*/
String codes = include
.getConcept()
.stream()
.filter(Objects::nonNull)
.map(ValueSet.ConceptReferenceComponent::getCode)
.filter(StringUtils::isNotBlank)
.collect(Collectors.joining(" "));
if (isNotBlank(codes)) {
bool.must(qb.keyword().onField("myCode").matching(codes).createQuery());
}
/*
* Filters
*/
@ -559,6 +537,32 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
}
Query luceneQuery = bool.createQuery();
/*
* Include Concepts
*/
List<Term> codes = include
.getConcept()
.stream()
.filter(Objects::nonNull)
.map(ValueSet.ConceptReferenceComponent::getCode)
.filter(StringUtils::isNotBlank)
.map(t->new Term("myCode", t))
.collect(Collectors.toList());
if (codes.size() > 0) {
MultiPhraseQuery query = new MultiPhraseQuery();
query.add(codes.toArray(new Term[0]));
luceneQuery = new BooleanQuery.Builder()
.add(luceneQuery, BooleanClause.Occur.MUST)
.add(query, BooleanClause.Occur.MUST)
.build();
}
/*
* Execute the query
*/
FullTextQuery jpaQuery = em.createFullTextQuery(luceneQuery, TermConcept.class);
jpaQuery.setMaxResults(1000);
@ -896,9 +900,11 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
for (TermConcept nextConcept : concepts) {
if (isBlank(nextConcept.getParentPidsAsString())) {
StringBuilder parentsBuilder = new StringBuilder();
createParentsString(parentsBuilder, nextConcept.getId());
nextConcept.setParentPids(parentsBuilder.toString());
}
saveConcept(nextConcept);
count++;
@ -932,6 +938,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
if (theConcept.getId() == null || theConcept.getIndexStatus() == null) {
retVal++;
theConcept.setIndexStatus(BaseHapiFhirDao.INDEX_STATUS_INDEXED);
theConcept.setUpdated(new Date());
myConceptDao.save(theConcept);
for (TermConceptProperty next : theConcept.getProperties()) {

View File

@ -150,6 +150,11 @@ public class JpaConstants {
*/
public static final String OPERATION_EVERYTHING = "$everything";
/**
* Operation name for the $process-message operation
*/
public static final String OPERATION_PROCESS_MESSAGE = "$process-message";
/**
* Operation name for the $meta-delete operation
*/

View File

@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@ -43,11 +44,6 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
private Exception myLastStackTrace;
private String myLastStackTraceThreadName;
@Bean(name="maxDatabaseThreadsForTest")
public Integer getMaxThread(){
return ourMaxThreads;
}
@Bean()
public DaoConfig daoConfig() {
return new DaoConfig();
@ -131,6 +127,11 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
return retVal;
}
@Bean(name = "maxDatabaseThreadsForTest")
public Integer getMaxThread() {
return ourMaxThreads;
}
private Properties jpaProperties() {
Properties extraProperties = new Properties();
extraProperties.put("hibernate.format_sql", "true");
@ -165,4 +166,9 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
return retVal;
}
@Bean
public UnregisterScheduledProcessor unregisterScheduledProcessor(Environment theEnv) {
return new UnregisterScheduledProcessor(theEnv);
}
}

View File

@ -6,14 +6,9 @@ import ca.uhn.fhir.jpa.subscription.email.IEmailSender;
import ca.uhn.fhir.jpa.subscription.email.JavaMailEmailSender;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.apache.commons.dbcp2.BasicDataSource;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
@ -22,13 +17,11 @@ import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.Environment;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
@ -194,23 +187,4 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
}
public class UnregisterScheduledProcessor implements BeanFactoryPostProcessor {
private final Environment myEnvironment;
public UnregisterScheduledProcessor(Environment theEnv) {
myEnvironment = theEnv;
}
@Override
public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) throws BeansException {
String schedulingDisabled = myEnvironment.getProperty("scheduling_disabled");
if ("true".equals(schedulingDisabled)) {
for (String beanName : beanFactory.getBeanNamesForType(ScheduledAnnotationBeanPostProcessor.class)) {
((DefaultListableBeanFactory) beanFactory).removeBeanDefinition(beanName);
}
}
}
}
}

View File

@ -7,15 +7,13 @@ import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder;
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.apache.commons.dbcp2.BasicDataSource;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.hibernate.query.criteria.LiteralHandlingMode;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.orm.hibernate5.HibernateExceptionTranslator;
import org.springframework.core.env.Environment;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
@ -163,6 +161,11 @@ public class TestR4Config extends BaseJavaConfigR4 {
return retVal;
}
@Bean
public UnregisterScheduledProcessor unregisterScheduledProcessor(Environment theEnv) {
return new UnregisterScheduledProcessor(theEnv);
}
public static int getMaxThreads() {
return ourMaxThreads;
}

View File

@ -0,0 +1,39 @@
package ca.uhn.fhir.jpa.config;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;
import org.springframework.scheduling.concurrent.ExecutorConfigurationSupport;
/**
* This bean postprocessor disables all scheduled tasks. It is intended
* only to be used in unit tests in circumstances where scheduled
* tasks cause issues.
*/
public class UnregisterScheduledProcessor implements BeanFactoryPostProcessor {
private final Environment myEnvironment;
public UnregisterScheduledProcessor(Environment theEnv) {
myEnvironment = theEnv;
}
@Override
public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) throws BeansException {
String schedulingDisabled = myEnvironment.getProperty("scheduling_disabled");
if ("true".equals(schedulingDisabled)) {
for (String beanName : beanFactory.getBeanNamesForType(ScheduledAnnotationBeanPostProcessor.class)) {
((DefaultListableBeanFactory) beanFactory).removeBeanDefinition(beanName);
}
for (String beanName : beanFactory.getBeanNamesForType(ExecutorConfigurationSupport.class)) {
ExecutorConfigurationSupport executorConfigSupport = ((DefaultListableBeanFactory) beanFactory).getBean(beanName, ExecutorConfigurationSupport.class);
executorConfigSupport.shutdown();
}
}
}
}

View File

@ -104,6 +104,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
@Qualifier("myConceptMapDaoR4")
protected IFhirResourceDaoConceptMap<ConceptMap> myConceptMapDao;
@Autowired
protected ITermConceptDao myTermConceptDao;
@Autowired
@Qualifier("myConditionDaoR4")
protected IFhirResourceDao<Condition> myConditionDao;
@Autowired

View File

@ -0,0 +1,109 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.util.TestUtil;
import net.ttddyy.dsproxy.QueryCountHolder;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.Patient;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Test;
import org.springframework.test.context.TestPropertySource;
import static org.junit.Assert.*;
@TestPropertySource(properties = {
"scheduling_disabled=true"
})
public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4QueryCountTest.class);
@After
public void afterResetDao() {
myDaoConfig.setResourceMetaCountHardLimit(new DaoConfig().getResourceMetaCountHardLimit());
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
}
@Test
public void testCreateClientAssignedId() {
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
QueryCountHolder.clear();
ourLog.info("** Starting Update Non-Existing resource with client assigned ID");
Patient p = new Patient();
p.setId("A");
p.getPhotoFirstRep().setCreationElement(new DateTimeType("2011")); // non-indexed field
myPatientDao.update(p).getId().toUnqualifiedVersionless();
assertEquals(1, QueryCountHolder.getGrandTotal().getSelect());
assertEquals(4, QueryCountHolder.getGrandTotal().getInsert());
assertEquals(0, QueryCountHolder.getGrandTotal().getDelete());
// Because of the forced ID's bidirectional link HFJ_RESOURCE <-> HFJ_FORCED_ID
assertEquals(1, QueryCountHolder.getGrandTotal().getUpdate());
runInTransaction(() -> {
assertEquals(1, myResourceTableDao.count());
assertEquals(1, myResourceHistoryTableDao.count());
assertEquals(1, myForcedIdDao.count());
assertEquals(1, myResourceIndexedSearchParamTokenDao.count());
});
// Ok how about an update
QueryCountHolder.clear();
ourLog.info("** Starting Update Existing resource with client assigned ID");
p = new Patient();
p.setId("A");
p.getPhotoFirstRep().setCreationElement(new DateTimeType("2012")); // non-indexed field
myPatientDao.update(p).getId().toUnqualifiedVersionless();
assertEquals(5, QueryCountHolder.getGrandTotal().getSelect());
assertEquals(1, QueryCountHolder.getGrandTotal().getInsert());
assertEquals(0, QueryCountHolder.getGrandTotal().getDelete());
assertEquals(1, QueryCountHolder.getGrandTotal().getUpdate());
runInTransaction(() -> {
assertEquals(1, myResourceTableDao.count());
assertEquals(2, myResourceHistoryTableDao.count());
assertEquals(1, myForcedIdDao.count());
assertEquals(1, myResourceIndexedSearchParamTokenDao.count());
});
}
@Test
public void testOneRowPerUpdate() {
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
QueryCountHolder.clear();
Patient p = new Patient();
p.getPhotoFirstRep().setCreationElement(new DateTimeType("2011")); // non-indexed field
IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless();
assertEquals(3, QueryCountHolder.getGrandTotal().getInsert());
runInTransaction(() -> {
assertEquals(1, myResourceTableDao.count());
assertEquals(1, myResourceHistoryTableDao.count());
});
QueryCountHolder.clear();
p = new Patient();
p.setId(id);
p.getPhotoFirstRep().setCreationElement(new DateTimeType("2012")); // non-indexed field
myPatientDao.update(p).getId().toUnqualifiedVersionless();
assertEquals(1, QueryCountHolder.getGrandTotal().getInsert());
runInTransaction(() -> {
assertEquals(1, myResourceTableDao.count());
assertEquals(2, myResourceHistoryTableDao.count());
});
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -1,30 +1,13 @@
package ca.uhn.fhir.jpa.dao.r4;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsStringIgnoringCase;
import static org.hamcrest.Matchers.empty;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.util.*;
import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.AllergyIntolerance.AllergyIntoleranceCategory;
import org.hl7.fhir.r4.model.AllergyIntolerance.AllergyIntoleranceClinicalStatus;
import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode;
import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r4.model.ValueSet.*;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.*;
import org.springframework.beans.factory.annotation.Autowired;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.param.TokenParam;
@ -34,13 +17,29 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.AllergyIntolerance.AllergyIntoleranceCategory;
import org.hl7.fhir.r4.model.AllergyIntolerance.AllergyIntoleranceClinicalStatus;
import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode;
import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r4.model.ValueSet.*;
import org.junit.*;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4TerminologyTest.class);
public static final String URL_MY_CODE_SYSTEM = "http://example.com/my_code_system";
public static final String URL_MY_VALUE_SET = "http://example.com/my_value_set";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4TerminologyTest.class);
@Autowired
private IHapiTerminologySvc myHapiTerminologySvc;
@ -96,38 +95,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
TermConcept childCA = new TermConcept(cs, "childCA").setDisplay("Child CA");
parentC.addChild(childCA, RelationshipTypeEnum.ISA);
myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM,"SYSTEM NAME" , cs);
return codeSystem;
}
private CodeSystem createExternalCsLarge() {
CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl(URL_MY_CODE_SYSTEM);
codeSystem.setContent(CodeSystemContentMode.NOTPRESENT);
IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified();
ResourceTable table = myResourceTableDao.findById(id.getIdPartAsLong()).orElseThrow(IllegalStateException::new);
TermCodeSystemVersion cs = new TermCodeSystemVersion();
cs.setResource(table);
TermConcept parentA = new TermConcept(cs, "codeA").setDisplay("CodeA");
cs.getConcepts().add(parentA);
for (int i = 0; i < 450; i++) {
TermConcept childI = new TermConcept(cs, "subCodeA"+i).setDisplay("Sub-code A"+i);
parentA.addChild(childI, RelationshipTypeEnum.ISA);
}
TermConcept parentB = new TermConcept(cs, "codeB").setDisplay("CodeB");
cs.getConcepts().add(parentB);
for (int i = 0; i < 450; i++) {
TermConcept childI = new TermConcept(cs, "subCodeB"+i).setDisplay("Sub-code B"+i);
parentB.addChild(childI, RelationshipTypeEnum.ISA);
}
myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM,"SYSTEM NAME" , cs);
myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", cs);
return codeSystem;
}
@ -163,7 +131,38 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
TermConcept beagle = new TermConcept(cs, "beagle").setDisplay("Beagle");
dogs.addChild(beagle, RelationshipTypeEnum.ISA);
myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM,"SYSTEM NAME" , cs);
myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", cs);
return codeSystem;
}
private CodeSystem createExternalCsLarge() {
CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl(URL_MY_CODE_SYSTEM);
codeSystem.setContent(CodeSystemContentMode.NOTPRESENT);
IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified();
ResourceTable table = myResourceTableDao.findById(id.getIdPartAsLong()).orElseThrow(IllegalStateException::new);
TermCodeSystemVersion cs = new TermCodeSystemVersion();
cs.setResource(table);
TermConcept parentA = new TermConcept(cs, "codeA").setDisplay("CodeA");
cs.getConcepts().add(parentA);
for (int i = 0; i < 450; i++) {
TermConcept childI = new TermConcept(cs, "subCodeA" + i).setDisplay("Sub-code A" + i);
parentA.addChild(childI, RelationshipTypeEnum.ISA);
}
TermConcept parentB = new TermConcept(cs, "codeB").setDisplay("CodeB");
cs.getConcepts().add(parentB);
for (int i = 0; i < 450; i++) {
TermConcept childI = new TermConcept(cs, "subCodeB" + i).setDisplay("Sub-code B" + i);
parentB.addChild(childI, RelationshipTypeEnum.ISA);
}
myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", cs);
return codeSystem;
}
@ -252,6 +251,20 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
}
@Test
public void testConceptTimestamps() {
long start = System.currentTimeMillis() - 10;
createExternalCsDogs();
runInTransaction(() -> {
List<TermConcept> concepts = myTermConceptDao.findAll();
for (TermConcept next : concepts) {
assertTrue(next.getUpdated().getTime() > start);
}
});
}
@Test
public void testExpandInvalid() {
createExternalCsAndLocalVs();
@ -300,47 +313,6 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
}
// TODO: get this working
@Ignore
@Test
public void testExpandWithOpEquals() {
ValueSet result = myValueSetDao.expandByIdentifier("http://hl7.org/fhir/ValueSet/doc-typecodes", "");
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result));
}
@Test
public void testExpandWithCodesAndDisplayFilterPartialOnFilter() {
CodeSystem codeSystem = createExternalCsDogs();
ValueSet valueSet = new ValueSet();
valueSet.setUrl(URL_MY_VALUE_SET);
valueSet.getCompose()
.addInclude()
.setSystem(codeSystem.getUrl())
.addConcept(new ConceptReferenceComponent().setCode("hello"))
.addConcept(new ConceptReferenceComponent().setCode("goodbye"));
valueSet.getCompose()
.addInclude()
.setSystem(codeSystem.getUrl())
.addFilter()
.setProperty("concept")
.setOp(FilterOperator.ISA)
.setValue("dogs");
myValueSetDao.create(valueSet, mySrd);
ValueSet result = myValueSetDao.expand(valueSet, "lab");
logAndValidateValueSet(result);
assertEquals(1, result.getExpansion().getTotal());
ArrayList<String> codes = toCodesContains(result.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("labrador"));
}
@Test
public void testExpandWithCodesAndDisplayFilterPartialOnCodes() {
CodeSystem codeSystem = createExternalCsDogs();
@ -389,6 +361,36 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
}
@Test
public void testExpandWithCodesAndDisplayFilterPartialOnFilter() {
CodeSystem codeSystem = createExternalCsDogs();
ValueSet valueSet = new ValueSet();
valueSet.setUrl(URL_MY_VALUE_SET);
valueSet.getCompose()
.addInclude()
.setSystem(codeSystem.getUrl())
.addConcept(new ConceptReferenceComponent().setCode("hello"))
.addConcept(new ConceptReferenceComponent().setCode("goodbye"));
valueSet.getCompose()
.addInclude()
.setSystem(codeSystem.getUrl())
.addFilter()
.setProperty("concept")
.setOp(FilterOperator.ISA)
.setValue("dogs");
myValueSetDao.create(valueSet, mySrd);
ValueSet result = myValueSetDao.expand(valueSet, "lab");
logAndValidateValueSet(result);
assertEquals(1, result.getExpansion().getTotal());
ArrayList<String> codes = toCodesContains(result.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("labrador"));
}
@Test
public void testExpandWithDisplayInExternalValueSetFuzzyMatching() {
createExternalCsAndLocalVs();
@ -442,6 +444,56 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
assertThat(codes, containsInAnyOrder("ParentA", "ParentB", "childAB", "childAAB", "ParentC", "childBA", "childCA"));
}
@Test
public void testExpandWithIncludeContainingDashesInInclude() {
CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl(URL_MY_CODE_SYSTEM);
codeSystem.setContent(CodeSystemContentMode.NOTPRESENT);
IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified();
ResourceTable table = myResourceTableDao.findById(id.getIdPartAsLong()).orElseThrow(IllegalStateException::new);
TermCodeSystemVersion cs = new TermCodeSystemVersion();
cs.setResource(table);
TermConcept concept;
concept = new TermConcept(cs, "LA1111-2");
cs.getConcepts().add(concept);
concept = new TermConcept(cs, "LA2222-2");
cs.getConcepts().add(concept);
concept = new TermConcept(cs, "LA3333-2");
cs.getConcepts().add(concept);
concept = new TermConcept(cs, "LA1122-2");
cs.getConcepts().add(concept);
concept = new TermConcept(cs, "LA1133-2");
cs.getConcepts().add(concept);
concept = new TermConcept(cs, "LA4444-2");
cs.getConcepts().add(concept);
concept = new TermConcept(cs, "LA9999-7");
cs.getConcepts().add(concept);
myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", cs);
ValueSet valueSet = new ValueSet();
valueSet.setUrl(URL_MY_VALUE_SET);
valueSet.getCompose()
.addInclude()
.setSystem(codeSystem.getUrl())
.addConcept(new ConceptReferenceComponent().setCode("LA2222-2"))
.addConcept(new ConceptReferenceComponent().setCode("LA1122-2"));
IIdType vsid = myValueSetDao.create(valueSet, mySrd).getId().toUnqualifiedVersionless();
ValueSet expansion = myValueSetDao.expand(vsid, null, null);
Set<String> codes = expansion
.getExpansion()
.getContains()
.stream()
.map(t -> t.getCode())
.collect(Collectors.toSet());
ourLog.info("Codes: {}", codes);
assertThat(codes, containsInAnyOrder("LA2222-2", "LA1122-2"));
}
@Test
public void testExpandWithInvalidExclude() {
createExternalCsAndLocalVs();
@ -543,6 +595,16 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
}
}
// TODO: get this working
@Ignore
@Test
public void testExpandWithOpEquals() {
ValueSet result = myValueSetDao.expandByIdentifier("http://hl7.org/fhir/ValueSet/doc-typecodes", "");
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result));
}
@Test
public void testExpandWithSystemAndCodesAndFilterKeywordInLocalValueSet() {
createLocalCsAndVs();
@ -713,7 +775,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
cs.setResource(table);
TermConcept parentA = new TermConcept(cs, "ParentA").setDisplay("Parent A");
cs.getConcepts().add(parentA);
myTermSvc.storeNewCodeSystemVersion(table.getId(), "http://snomed.info/sct","Snomed CT" , cs);
myTermSvc.storeNewCodeSystemVersion(table.getId(), "http://snomed.info/sct", "Snomed CT", cs);
StringType code = new StringType("ParentA");
StringType system = new StringType("http://snomed.info/sct");
@ -818,19 +880,6 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
}
@Test
public void testSearchCodeInUnknownCodeSystem() {
SearchParameterMap params = new SearchParameterMap();
try {
params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN));
assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty());
} catch (InvalidRequestException e) {
assertEquals("Unable to find imported value set http://example.com/my_value_set", e.getMessage());
}
}
@Test
public void testSearchCodeBelowBuiltInCodesystem() {
AllergyIntolerance ai1 = new AllergyIntolerance();
@ -918,33 +967,6 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
}
@Test
public void testSearchCodeBelowLocalCodesystem() {
createLocalCsAndVs();
Observation obsAA = new Observation();
obsAA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("AA");
IIdType idAA = myObservationDao.create(obsAA, mySrd).getId().toUnqualifiedVersionless();
Observation obsBA = new Observation();
obsBA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("BA");
IIdType idBA = myObservationDao.create(obsBA, mySrd).getId().toUnqualifiedVersionless();
Observation obsCA = new Observation();
obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA");
IIdType idCA = myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless();
SearchParameterMap params = new SearchParameterMap();
params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "A").setModifier(TokenParamModifier.BELOW));
assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), containsInAnyOrder(idAA.getValue()));
params = new SearchParameterMap();
params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "AAA").setModifier(TokenParamModifier.BELOW));
assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty());
}
@Test
public void testSearchCodeBelowExternalCodesystemLarge() {
createExternalCsLarge();
@ -975,6 +997,32 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
}
@Test
public void testSearchCodeBelowLocalCodesystem() {
createLocalCsAndVs();
Observation obsAA = new Observation();
obsAA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("AA");
IIdType idAA = myObservationDao.create(obsAA, mySrd).getId().toUnqualifiedVersionless();
Observation obsBA = new Observation();
obsBA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("BA");
IIdType idBA = myObservationDao.create(obsBA, mySrd).getId().toUnqualifiedVersionless();
Observation obsCA = new Observation();
obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA");
IIdType idCA = myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless();
SearchParameterMap params = new SearchParameterMap();
params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "A").setModifier(TokenParamModifier.BELOW));
assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), containsInAnyOrder(idAA.getValue()));
params = new SearchParameterMap();
params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "AAA").setModifier(TokenParamModifier.BELOW));
assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty());
}
@Test
public void testSearchCodeInBuiltInValueSet() {
AllergyIntolerance ai1 = new AllergyIntolerance();
@ -1093,7 +1141,6 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
assertThat(toUnqualifiedVersionlessIdValues(myAuditEventDao.search(params)), empty());
}
@Test
public void testSearchCodeInLocalCodesystem() {
createLocalCsAndVs();
@ -1116,6 +1163,19 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
}
@Test
public void testSearchCodeInUnknownCodeSystem() {
SearchParameterMap params = new SearchParameterMap();
try {
params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN));
assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty());
} catch (InvalidRequestException e) {
assertEquals("Unable to find imported value set http://example.com/my_value_set", e.getMessage());
}
}
@Test
public void testSearchCodeInValueSetThatImportsInvalidCodeSystem() {
ValueSet valueSet = new ValueSet();
@ -1131,7 +1191,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN));
try {
myObservationDao.search(params);
} catch(InvalidRequestException e) {
} catch (InvalidRequestException e) {
assertEquals("Unable to expand imported value set: Unable to find imported value set http://non_existant_VS", e.getMessage());
}

View File

@ -39,9 +39,7 @@ public class FhirResourceDaoR4UpdateTagSnapshotTest extends BaseJpaR4Test {
myPatientDao.update(p, mySrd);
p = myPatientDao.read(new IdType("A"), mySrd);
// It would be nice if this didn't trigger a version update but
// i guess it's not so bad that it does
assertEquals("2", p.getIdElement().getVersionIdPart());
assertEquals("1", p.getIdElement().getVersionIdPart());
assertEquals(true, p.getActive());
assertEquals(1, p.getMeta().getTag().size());
}
@ -86,9 +84,7 @@ public class FhirResourceDaoR4UpdateTagSnapshotTest extends BaseJpaR4Test {
myPatientDao.update(p, mySrd);
p = myPatientDao.read(new IdType("A"), mySrd);
// It would be nice if this didn't trigger a version update but
// i guess it's not so bad that it does
assertEquals("2", p.getIdElement().getVersionIdPart());
assertEquals("1", p.getIdElement().getVersionIdPart());
assertEquals(true, p.getActive());
assertEquals(1, p.getMeta().getTag().size());
assertEquals("urn:foo", p.getMeta().getTag().get(0).getSystem());
@ -136,9 +132,7 @@ public class FhirResourceDaoR4UpdateTagSnapshotTest extends BaseJpaR4Test {
p = myPatientDao.read(new IdType("A"), mySrd);
assertEquals(true, p.getActive());
assertEquals(0, p.getMeta().getTag().size());
// It would be nice if this didn't trigger a version update but
// i guess it's not so bad that it does
assertEquals("2", p.getIdElement().getVersionIdPart());
assertEquals("1", p.getIdElement().getVersionIdPart());
}
@AfterClass

View File

@ -25,7 +25,11 @@ import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.TestUtil;
import org.springframework.test.context.TestPropertySource;
@TestPropertySource(properties = {
"scheduling_disabled=true"
})
public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4UpdateTest.class);

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.provider;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
@ -20,10 +21,7 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.TestUtil;
@ -87,12 +85,12 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
mySearchCoordinatorSvcRaw = AopTestUtils.getTargetObject(mySearchCoordinatorSvc);
}
private void checkParamMissing(String paramName) throws IOException, ClientProtocolException {
private void checkParamMissing(String paramName) throws IOException {
HttpGet get = new HttpGet(ourServerBase + "/Observation?" + paramName + ":missing=false");
CloseableHttpResponse resp = ourHttpClient.execute(get);
IOUtils.closeQuietly(resp.getEntity().getContent());
try (CloseableHttpResponse resp = ourHttpClient.execute(get)) {
assertEquals(200, resp.getStatusLine().getStatusCode());
}
}
/**
* See #484
@ -186,7 +184,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
@Test
public void testCountParam() throws Exception {
public void testCountParam() {
// NB this does not get used- The paging provider has its own limits built in
myDaoConfig.setHardSearchLimit(100);
@ -224,7 +222,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
* See #438
*/
@Test
public void testCreateAndUpdateBinary() throws ClientProtocolException, Exception {
public void testCreateAndUpdateBinary() throws Exception {
byte[] arr = {1, 21, 74, 123, -44};
Binary binary = new Binary();
binary.setContent(arr);
@ -289,7 +287,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
@Test
public void testCreateQuestionnaireResponseWithValidation() throws IOException {
public void testCreateQuestionnaireResponseWithValidation() {
ValueSet options = new ValueSet();
options.getCodeSystem().setSystem("urn:system").addConcept().setCode("code0");
IIdType optId = ourClient.create().resource(options).execute().getId();
@ -419,6 +417,27 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
}
@Test
public void testCreateResourceWithNumericId() throws IOException {
String resource = "<Patient xmlns=\"http://hl7.org/fhir\"></Patient>";
HttpPost post = new HttpPost(ourServerBase + "/Patient/2");
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post);
try {
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseString);
assertEquals(400, response.getStatusLine().getStatusCode());
OperationOutcome oo = myFhirCtx.newXmlParser().parseResource(OperationOutcome.class, responseString);
assertEquals("Can not create resource with ID \"2\", ID must not be supplied on a create (POST) operation (use an HTTP PUT / update operation if you wish to supply an ID)",
oo.getIssue().get(0).getDiagnostics());
} finally {
response.getEntity().getContent().close();
response.close();
}
}
// private void delete(String theResourceType, String theParamName, String theParamValue) {
// Bundle resources;
// do {
@ -445,28 +464,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
// }
@Test
public void testCreateResourceWithNumericId() throws IOException {
String resource = "<Patient xmlns=\"http://hl7.org/fhir\"></Patient>";
HttpPost post = new HttpPost(ourServerBase + "/Patient/2");
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post);
try {
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseString);
assertEquals(400, response.getStatusLine().getStatusCode());
OperationOutcome oo = myFhirCtx.newXmlParser().parseResource(OperationOutcome.class, responseString);
assertEquals("Can not create resource with ID \"2\", ID must not be supplied on a create (POST) operation (use an HTTP PUT / update operation if you wish to supply an ID)",
oo.getIssue().get(0).getDiagnostics());
} finally {
response.getEntity().getContent().close();
response.close();
}
}
@Test
public void testCreateWithForcedId() throws IOException {
public void testCreateWithForcedId() {
String methodName = "testCreateWithForcedId";
Patient p = new Patient();
@ -628,7 +626,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
* Based on email from Rene Spronk
*/
@Test
public void testDeleteResourceConditional2() throws IOException, Exception {
public void testDeleteResourceConditional2() throws Exception {
String methodName = "testDeleteResourceConditional2";
Patient pt = new Patient();
@ -695,7 +693,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
* See issue #52
*/
@Test
public void testDiagnosticOrderResources() throws Exception {
public void testDiagnosticOrderResources() {
IGenericClient client = ourClient;
int initialSize = client
@ -787,7 +785,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
@Test
public void testEverythingEncounterInstance() throws Exception {
public void testEverythingEncounterInstance() {
String methodName = "testEverythingEncounterInstance";
Organization org1parent = new Organization();
@ -851,7 +849,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
@Test
public void testEverythingEncounterType() throws Exception {
public void testEverythingEncounterType() {
String methodName = "testEverythingEncounterInstance";
Organization org1parent = new Organization();
@ -951,7 +949,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
List<IIdType> actual;
StringAndListParam param;
ourLog.info("Pt1:{} Pt2:{} Obs1:{} Obs2:{} Obs3:{}", new Object[] {ptId1.getIdPart(), ptId2.getIdPart(), obsId1.getIdPart(), obsId2.getIdPart(), obsId3.getIdPart()});
ourLog.info("Pt1:{} Pt2:{} Obs1:{} Obs2:{} Obs3:{}", ptId1.getIdPart(), ptId2.getIdPart(), obsId1.getIdPart(), obsId2.getIdPart(), obsId3.getIdPart());
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
@ -974,7 +972,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
* See #147
*/
@Test
public void testEverythingPatientDoesntRepeatPatient() throws Exception {
public void testEverythingPatientDoesntRepeatPatient() {
ca.uhn.fhir.model.dstu2.resource.Bundle b;
b = myFhirCtx.newJsonParser().parseResource(ca.uhn.fhir.model.dstu2.resource.Bundle.class, new InputStreamReader(ResourceProviderDstu2Test.class.getResourceAsStream("/bug147-bundle.json")));
@ -1033,7 +1031,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
* Test for #226
*/
@Test
public void testEverythingPatientIncludesBackReferences() throws Exception {
public void testEverythingPatientIncludesBackReferences() {
String methodName = "testEverythingIncludesBackReferences";
Medication med = new Medication();
@ -1060,7 +1058,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
* See #148
*/
@Test
public void testEverythingPatientIncludesCondition() throws Exception {
public void testEverythingPatientIncludesCondition() {
ca.uhn.fhir.model.dstu2.resource.Bundle b = new ca.uhn.fhir.model.dstu2.resource.Bundle();
Patient p = new Patient();
p.setId("1");
@ -1092,7 +1090,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
@Test
public void testEverythingPatientOperation() throws Exception {
public void testEverythingPatientOperation() {
String methodName = "testEverythingOperation";
Organization org1parent = new Organization();
@ -1137,7 +1135,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
@Test
public void testEverythingPatientType() throws Exception {
public void testEverythingPatientType() {
String methodName = "testEverythingPatientType";
Organization o1 = new Organization();
@ -1451,7 +1449,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
@Test
public void testMetaOperations() throws Exception {
public void testMetaOperations() {
String methodName = "testMetaOperations";
Patient pt = new Patient();
@ -1488,7 +1486,6 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
}
@Test
public void testPagingOverEverythingSet() throws InterruptedException {
Patient p = new Patient();
@ -1543,7 +1540,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
@Test
public void testPagingOverEverythingSetWithNoPagingProvider() throws InterruptedException {
public void testPagingOverEverythingSetWithNoPagingProvider() {
ourRestServer.setPagingProvider(null);
Patient p = new Patient();
@ -1576,11 +1573,30 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
@Test
public void testProcessMessage() {
Bundle bundle = new Bundle();
bundle.setType(BundleTypeEnum.MESSAGE);
Parameters parameters = new Parameters();
parameters.addParameter()
.setName("content")
.setResource(bundle);
try {
ourClient.operation().onType(MessageHeader.class).named(JpaConstants.OPERATION_PROCESS_MESSAGE).withParameters(parameters).execute();
fail();
} catch (NotImplementedOperationException e) {
assertThat(e.getMessage(), containsString("This operation is not yet implemented on this server"));
}
}
/**
* Test for issue #60
*/
@Test
public void testReadAllInstancesOfType() throws Exception {
public void testReadAllInstancesOfType() {
Patient pat;
pat = new Patient();
@ -1960,7 +1976,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
private void testSearchReturnsResults(String search) throws IOException, ClientProtocolException {
private void testSearchReturnsResults(String search) throws IOException {
int matches;
HttpGet get = new HttpGet(ourServerBase + search);
CloseableHttpResponse response = ourHttpClient.execute(get);
@ -2001,7 +2017,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
@Test
public void testSearchWithInclude() throws Exception {
public void testSearchWithInclude() {
Organization org = new Organization();
org.addIdentifier().setSystem("urn:system:rpdstu2").setValue("testSearchWithInclude01");
IdDt orgId = (IdDt) ourClient.create().resource(org).prettyPrint().encodedXml().execute().getId();
@ -2029,7 +2045,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
@Test(expected = InvalidRequestException.class)
public void testSearchWithInvalidSort() throws Exception {
public void testSearchWithInvalidSort() {
Observation o = new Observation();
o.getCode().setText("testSearchWithInvalidSort");
myObservationDao.create(o, mySrd);
@ -2042,7 +2058,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
@Test
public void testSearchWithMissing() throws Exception {
public void testSearchWithMissing() {
ourLog.info("Starting testSearchWithMissing");
String methodName = "testSearchWithMissing";
@ -2286,7 +2302,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
* Test for issue #60
*/
@Test
public void testStoreUtf8Characters() throws Exception {
public void testStoreUtf8Characters() {
Organization org = new Organization();
org.setName("測試醫院");
org.addIdentifier().setSystem("urn:system").setValue("testStoreUtf8Characters_01");
@ -2340,7 +2356,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
@Test
public void testUpdateInvalidUrl() throws IOException, Exception {
public void testUpdateInvalidUrl() throws Exception {
String methodName = "testUpdateInvalidReference";
Patient pt = new Patient();
@ -2362,7 +2378,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
@Test
public void testUpdateRejectsInvalidTypes() throws InterruptedException {
public void testUpdateRejectsInvalidTypes() {
Patient p1 = new Patient();
p1.addIdentifier().setSystem("urn:system").setValue("testUpdateRejectsInvalidTypes");
@ -2467,7 +2483,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
@Test
public void testUpdateResourceWithPrefer() throws IOException, Exception {
public void testUpdateResourceWithPrefer() throws Exception {
String methodName = "testUpdateResourceWithPrefer";
Patient pt = new Patient();
@ -2680,7 +2696,6 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
Patient patient = new Patient();
patient.addName().addGiven("James" + StringUtils.leftPad("James", 1000000, 'A'));
;
patient.setBirthDate(new DateDt("2011-02-02"));
Parameters input = new Parameters();

View File

@ -1,31 +1,29 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import static org.junit.Assert.*;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Bundle.BundleType;
import org.hl7.fhir.dstu3.model.Composition;
import org.hl7.fhir.dstu3.model.MessageHeader;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.AfterClass;
import org.junit.Test;
import ca.uhn.fhir.util.TestUtil;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.*;
public class ResourceProviderDstu3BundleTest extends BaseResourceProviderDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderDstu3BundleTest.class);
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
/**
* See #401
*/
@Test
public void testBundlePreservesFullUrl() throws Exception {
public void testBundlePreservesFullUrl() {
Bundle bundle = new Bundle();
bundle.setType(BundleType.DOCUMENT);
@ -42,5 +40,29 @@ public class ResourceProviderDstu3BundleTest extends BaseResourceProviderDstu3Te
assertEquals("http://foo", bundle.getEntry().get(0).getFullUrl());
}
@Test
public void testProcessMessage() {
Bundle bundle = new Bundle();
bundle.setType(Bundle.BundleType.MESSAGE);
Parameters parameters = new Parameters();
parameters.addParameter()
.setName("content")
.setResource(bundle);
try {
ourClient.operation().onType(MessageHeader.class).named(JpaConstants.OPERATION_PROCESS_MESSAGE).withParameters(parameters).execute();
fail();
} catch (NotImplementedOperationException e) {
assertThat(e.getMessage(), containsString("This operation is not yet implemented on this server"));
}
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept;
@ -10,8 +11,11 @@ import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
@ -109,6 +113,16 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
myLocalValueSetId = myValueSetDao.create(myLocalVs, mySrd).getId().toUnqualifiedVersionless();
}
private void createLocalVsWithIncludeConcept() {
myLocalVs = new ValueSet();
myLocalVs.setUrl(URL_MY_VALUE_SET);
ConceptSetComponent include = myLocalVs.getCompose().addInclude();
include.setSystem(URL_MY_CODE_SYSTEM);
include.addConcept().setCode("A");
include.addConcept().setCode("AA");
myLocalValueSetId = myValueSetDao.create(myLocalVs, mySrd).getId().toUnqualifiedVersionless();
}
private void createLocalVsWithUnknownCode(CodeSystem codeSystem) {
myLocalVs = new ValueSet();
myLocalVs.setUrl(URL_MY_VALUE_SET);
@ -172,7 +186,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
* $expand?identifier=foo is legacy.. It's actually not valid in FHIR as of STU3
* but we supported it for longer than we should have so I don't want to delete
* it right now.
*
* <p>
* https://groups.google.com/d/msgid/hapi-fhir/CAN2Cfy8kW%2BAOkgC6VjPsU3gRCpExCNZBmJdi-k5R_TWeyWH4tA%40mail.gmail.com?utm_medium=email&utm_source=footer
*/
@Test
@ -462,6 +476,29 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
}
@Test
public void testValidateCodeOperationByCodeAndSystemInstanceOnInstance() throws IOException {
createLocalCsAndVs();
createLocalVsWithIncludeConcept();
String url = ourServerBase +
"/ValueSet/" + myLocalValueSetId.getIdPart() + "/$validate-code?system=" +
UrlUtil.escapeUrlParam(FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM) +
"&code=AA";
ourLog.info("* Requesting: {}", url);
HttpGet request = new HttpGet(url);
request.addHeader("Accept", "application/fhir+json");
try (CloseableHttpResponse response = ourHttpClient.execute(request)) {
String respString = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(respString);
Parameters respParam = myFhirCtx.newJsonParser().parseResource(Parameters.class, respString);
assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
}
}
@Test
public void testValidateCodeOperationByCodeAndSystemType() {
//@formatter:off

View File

@ -1,31 +1,29 @@
package ca.uhn.fhir.jpa.provider.r4;
import static org.junit.Assert.*;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.r4.model.MessageHeader;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Bundle.BundleType;
import org.hl7.fhir.r4.model.Composition;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Parameters;
import org.junit.AfterClass;
import org.junit.Test;
import ca.uhn.fhir.util.TestUtil;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.*;
public class ResourceProviderR4BundleTest extends BaseResourceProviderR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderR4BundleTest.class);
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
/**
* See #401
*/
@Test
public void testBundlePreservesFullUrl() throws Exception {
public void testBundlePreservesFullUrl() {
Bundle bundle = new Bundle();
bundle.setType(BundleType.DOCUMENT);
@ -34,13 +32,38 @@ public class ResourceProviderR4BundleTest extends BaseResourceProviderR4Test {
composition.setTitle("Visit Summary");
bundle.addEntry().setFullUrl("http://foo").setResource(composition);
IIdType id = ourClient.create().resource(bundle).execute().getId();
IIdType id = myClient.create().resource(bundle).execute().getId();
Bundle retBundle = myClient.read().resource(Bundle.class).withId(id).execute();
Bundle retBundle = ourClient.read().resource(Bundle.class).withId(id).execute();
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(retBundle));
assertEquals("http://foo", bundle.getEntry().get(0).getFullUrl());
}
@Test
public void testProcessMessage() {
Bundle bundle = new Bundle();
bundle.setType(BundleType.MESSAGE);
Parameters parameters = new Parameters();
parameters.addParameter()
.setName("content")
.setResource(bundle);
try {
myClient.operation().onType(MessageHeader.class).named(JpaConstants.OPERATION_PROCESS_MESSAGE).withParameters(parameters).execute();
fail();
} catch (NotImplementedOperationException e) {
assertThat(e.getMessage(), containsString("This operation is not yet implemented on this server"));
}
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -10,8 +10,11 @@ import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
@ -72,7 +75,6 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
}
private void createLocalCsAndVs() {
//@formatter:off
CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl(URL_MY_CODE_SYSTEM);
codeSystem.setContent(CodeSystemContentMode.COMPLETE);
@ -86,10 +88,17 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
.addConcept().setCode("B").setDisplay("Code B")
.addConcept(new ConceptDefinitionComponent().setCode("BA").setDisplay("Code BA"))
.addConcept(new ConceptDefinitionComponent().setCode("BB").setDisplay("Code BB"));
//@formatter:on
myCodeSystemDao.create(codeSystem, mySrd);
}
createLocalVs(codeSystem);
private void createLocalVsWithIncludeConcept() {
myLocalVs = new ValueSet();
myLocalVs.setUrl(URL_MY_VALUE_SET);
ConceptSetComponent include = myLocalVs.getCompose().addInclude();
include.setSystem(URL_MY_CODE_SYSTEM);
include.addConcept().setCode("A");
include.addConcept().setCode("AA");
myLocalValueSetId = myValueSetDao.create(myLocalVs, mySrd).getId().toUnqualifiedVersionless();
}
private void createLocalVs(CodeSystem codeSystem) {
@ -97,7 +106,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
myLocalVs.setUrl(URL_MY_VALUE_SET);
ConceptSetComponent include = myLocalVs.getCompose().addInclude();
include.setSystem(codeSystem.getUrl());
include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("childAA");
include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("ParentA");
myLocalValueSetId = myValueSetDao.create(myLocalVs, mySrd).getId().toUnqualifiedVersionless();
}
@ -119,7 +128,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
}
@Test
public void testExpandById() throws IOException {
public void testExpandById() {
//@formatter:off
Parameters respParam = ourClient
.operation()
@ -149,7 +158,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
}
@Test
public void testExpandByIdWithFilter() throws IOException {
public void testExpandByIdWithFilter() {
//@formatter:off
Parameters respParam = ourClient
@ -208,7 +217,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
@Test
public void testExpandInlineVsAgainstBuiltInCs() throws IOException {
public void testExpandInlineVsAgainstBuiltInCs() {
createLocalVsPointingAtBuiltInCodeSystem();
assertNotNull(myLocalValueSetId);
@ -229,7 +238,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
}
@Test
public void testExpandInlineVsAgainstExternalCs() throws IOException {
public void testExpandInlineVsAgainstExternalCs() {
createExternalCsAndLocalVs();
assertNotNull(myLocalVs);
myLocalVs.setId("");
@ -304,7 +313,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
}
@Test
public void testExpandLocalVsAgainstBuiltInCs() throws IOException {
public void testExpandLocalVsAgainstBuiltInCs() {
createLocalVsPointingAtBuiltInCodeSystem();
assertNotNull(myLocalValueSetId);
@ -325,7 +334,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
}
@Test
public void testExpandLocalVsAgainstExternalCs() throws IOException {
public void testExpandLocalVsAgainstExternalCs() {
createExternalCsAndLocalVs();
assertNotNull(myLocalValueSetId);
@ -349,7 +358,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
}
@Test
public void testExpandLocalVsCanonicalAgainstExternalCs() throws IOException {
public void testExpandLocalVsCanonicalAgainstExternalCs() {
createExternalCsAndLocalVs();
assertNotNull(myLocalValueSetId);
@ -373,7 +382,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
}
@Test
public void testExpandLocalVsWithUnknownCode() throws IOException {
public void testExpandLocalVsWithUnknownCode() {
createExternalCsAndLocalVsWithUnknownCode();
assertNotNull(myLocalValueSetId);
@ -400,8 +409,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
HttpPost post = new HttpPost(ourServerBase + "/ValueSet/%24expand");
post.setEntity(new StringEntity(string, ContentType.parse(ca.uhn.fhir.rest.api.Constants.CT_FHIR_JSON_NEW)));
CloseableHttpResponse resp = ourHttpClient.execute(post);
try {
try (CloseableHttpResponse resp = ourHttpClient.execute(post)) {
String respString = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(respString);
@ -411,8 +419,6 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
assertEquals(400, resp.getStatusLine().getStatusCode());
assertThat(respString, containsString("Unknown FilterOperator code 'n'"));
} finally {
IOUtils.closeQuietly(resp);
}
}
@ -426,7 +432,6 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
.withParameter(Parameters.class, "code", new CodeType("8495-4"))
.andParameter("system", new UriType("http://acme.org"))
.execute();
//@formatter:on
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
@ -434,6 +439,49 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
}
@Test
public void testValidateCodeOperationByCodeAndSystemInstanceOnType() throws IOException {
createLocalCsAndVs();
String url = ourServerBase +
"/ValueSet/$validate-code?system=" +
UrlUtil.escapeUrlParam(URL_MY_CODE_SYSTEM) +
"&code=AA";
HttpGet request = new HttpGet(url);
request.addHeader("Accept", "application/fhir+json");
try (CloseableHttpResponse response = ourHttpClient.execute(request)) {
String respString = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(respString);
Parameters respParam = myFhirCtx.newJsonParser().parseResource(Parameters.class, respString);
assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
}
}
@Test
public void testValidateCodeOperationByCodeAndSystemInstanceOnInstance() throws IOException {
createLocalCsAndVs();
createLocalVsWithIncludeConcept();
String url = ourServerBase +
"/ValueSet/" + myLocalValueSetId.getIdPart() + "/$validate-code?system=" +
UrlUtil.escapeUrlParam(URL_MY_CODE_SYSTEM) +
"&code=AA";
ourLog.info("* Requesting: {}", url);
HttpGet request = new HttpGet(url);
request.addHeader("Accept", "application/fhir+json");
try (CloseableHttpResponse response = ourHttpClient.execute(request)) {
String respString = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(respString);
Parameters respParam = myFhirCtx.newJsonParser().parseResource(Parameters.class, respString);
assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
}
}
@Test
public void testValidateCodeOperationByCodeAndSystemType() {
//@formatter:off
@ -444,7 +492,6 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
.withParameter(Parameters.class, "code", new CodeType("8450-9"))
.andParameter("system", new UriType("http://acme.org"))
.execute();
//@formatter:on
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Lists;
@ -52,17 +53,6 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
.findFirst();
}
private <T extends Type> Optional<T> getPropertyPart(Parameters theParameters, String thePropName, String thePart) {
return theParameters
.getParameter()
.stream()
.filter(t -> t.getName().equals(thePropName))
.flatMap(t -> t.getPart().stream())
.filter(t -> t.getName().equals(thePart))
.map(t -> (T) t.getValue())
.findFirst();
}
@Test
public void testExpandWithPropertyCoding() throws Exception {
ZipCollectionBuilder files = new ZipCollectionBuilder();
@ -169,7 +159,6 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
}
@Test
public void testLookupWithProperties2() throws Exception {
ZipCollectionBuilder files = new ZipCollectionBuilder();
@ -186,10 +175,9 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
assertTrue(propertyValue.isPresent());
assertEquals(IHapiTerminologyLoaderSvc.LOINC_URI, propertyValue.get().getSystem());
assertEquals("LP19258-0", propertyValue.get().getCode());
assertEquals("Qn", propertyValue.get().getDisplay());
assertEquals("Large unstained cells/100 leukocytes", propertyValue.get().getDisplay());
}
@Test
public void testLookupWithPropertiesExplicit() throws Exception {
ZipCollectionBuilder files = new ZipCollectionBuilder();
@ -214,6 +202,30 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
}
@Test
public void testValidateCodeFound() throws Exception {
ZipCollectionBuilder files = new ZipCollectionBuilder();
TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files);
myLoader.loadLoinc(files.getFiles(), mySrd);
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(null, null, new StringType("10013-1"), new StringType(IHapiTerminologyLoaderSvc.LOINC_URI), null, null, null, mySrd);
assertTrue(result.isResult());
assertEquals("Found code", result.getMessage());
}
@Test
public void testValidateCodeNotFound() throws Exception {
ZipCollectionBuilder files = new ZipCollectionBuilder();
TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files);
myLoader.loadLoinc(files.getFiles(), mySrd);
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(null, null, new StringType("10013-1-9999999999"), new StringType(IHapiTerminologyLoaderSvc.LOINC_URI), null, null, null, mySrd);
assertFalse(result.isResult());
assertEquals("Code not found", result.getMessage());
}
private Set<String> toExpandedCodes(ValueSet theExpanded) {
return theExpanded
.getExpansion()

View File

@ -137,6 +137,39 @@ drop table cdr_xact_log_step cascade constraints;
drop table cdr_xact_log cascade constraints;
drop table hfj_history_tag cascade;
drop table hfj_forced_id cascade;
drop table hfj_res_link cascade;
drop table hfj_spidx_coords cascade;
drop table hfj_spidx_date cascade;
drop table hfj_spidx_number cascade;
drop table hfj_spidx_quantity cascade;
drop table hfj_spidx_string cascade;
drop table hfj_spidx_token cascade;
drop table hfj_spidx_uri cascade;
drop table hfj_res_tag cascade;
drop table hfj_search_result cascade;
drop table hfj_search_include cascade;
drop table hfj_search cascade;
drop table hfj_res_param_present cascade;
drop table hfj_idx_cmp_string_uniq cascade;
drop table hfj_subscription_stats cascade;
drop table trm_concept_property cascade;
drop table trm_concept_pc_link cascade;
drop table trm_concept cascade;
drop table trm_codesystem_ver cascade;
drop table trm_codesystem cascade;
DROP TABLE hfj_resource cascade;
DROP TABLE hfj_res_ver cascade;
drop table hfj_search_parm cascade;
drop table hfj_tag_def cascade;
drop index IDX_FORCEDID_TYPE_FORCEDID;
alter table hfj_forced_id drop constraint idx_forcedid_type_resid;
Upgrading
drop index IDX_SP_STRING;
create index IDX_SP_STRING_HASH_NRM;
@ -153,6 +186,7 @@ drop index IDX_SP_QUANTITY;
create index IDX_SP_QUANTITY_HASH;
create index IDX_SP_QUANTITY_HASH_UN;
drop index IDX_FORCEDID_TYPE_FORCEDID;
alter table hfj_forced_id drop constraint idx_forcedid_type_resid;
create index IDX_FORCEDID_TYPE_FID;
drop index IDX_SP_NUMBER;
create index IDX_SP_NUMBER_HASH_VAL;

View File

@ -7,6 +7,7 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor;
import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -19,6 +20,8 @@ import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.*;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank;
@ -184,6 +187,21 @@ public abstract class RequestDetails {
public void setParameters(Map<String, String[]> theParams) {
myParameters = theParams;
myUnqualifiedToQualifiedNames = null;
// Sanitize keys if necessary to prevent injection attacks
boolean needsSanitization = false;
for (String nextKey : theParams.keySet()) {
if (UrlUtil.isNeedsSanitization(nextKey)) {
needsSanitization = true;
break;
}
}
if (needsSanitization) {
myParameters = myParameters
.entrySet()
.stream()
.collect(Collectors.toMap(t -> UrlUtil.sanitizeUrlPart((String) ((Map.Entry) t).getKey()), t -> (String[]) ((Map.Entry) t).getValue()));
}
}
/**

View File

@ -1246,7 +1246,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
String operation = null;
String compartment = null;
if (tok.hasMoreTokens()) {
resourceName = tok.nextToken();
resourceName = tok.nextTokenUnescapedAndSanitized();
if (partIsOperation(resourceName)) {
operation = resourceName;
resourceName = null;
@ -1255,7 +1255,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
theRequestDetails.setResourceName(resourceName);
if (tok.hasMoreTokens()) {
String nextString = tok.nextToken();
String nextString = tok.nextTokenUnescapedAndSanitized();
if (partIsOperation(nextString)) {
operation = nextString;
} else {
@ -1265,10 +1265,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
if (tok.hasMoreTokens()) {
String nextString = tok.nextToken();
String nextString = tok.nextTokenUnescapedAndSanitized();
if (nextString.equals(Constants.PARAM_HISTORY)) {
if (tok.hasMoreTokens()) {
String versionString = tok.nextToken();
String versionString = tok.nextTokenUnescapedAndSanitized();
if (id == null) {
throw new InvalidRequestException("Don't know how to handle request path: " + theRequestPath);
}
@ -1290,7 +1290,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
String secondaryOperation = null;
while (tok.hasMoreTokens()) {
String nextString = tok.nextToken();
String nextString = tok.nextTokenUnescapedAndSanitized();
if (operation == null) {
operation = nextString;
} else if (secondaryOperation == null) {

View File

@ -122,7 +122,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
myOtherOperatiopnType = RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE;
}
myReturnParams = new ArrayList<OperationMethodBinding.ReturnType>();
myReturnParams = new ArrayList<>();
if (theReturnParams != null) {
for (OperationParam next : theReturnParams) {
ReturnType type = new ReturnType();

View File

@ -43,7 +43,7 @@ public class UrlBaseTenantIdentificationStrategy implements ITenantIdentificatio
public void extractTenant(UrlPathTokenizer theUrlPathTokenizer, RequestDetails theRequestDetails) {
String tenantId = null;
if (theUrlPathTokenizer.hasMoreTokens()) {
tenantId = defaultIfBlank(theUrlPathTokenizer.nextToken(), null);
tenantId = defaultIfBlank(theUrlPathTokenizer.nextTokenUnescapedAndSanitized(), null);
ourLog.trace("Found tenant ID {} in request string", tenantId);
theRequestDetails.setTenantId(tenantId);
}

View File

@ -958,6 +958,8 @@ public class ClientR4Test {
}
@Test
public void testSearchWithStringIncludes() throws Exception {

View File

@ -13,6 +13,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.ReaderInputStream;
import org.apache.commons.lang3.StringUtils;
@ -44,6 +45,7 @@ import java.io.StringReader;
import java.nio.charset.Charset;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -228,7 +230,7 @@ public class GenericClientTest {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") });
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] {new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22")});
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
@ -255,7 +257,7 @@ public class GenericClientTest {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK"));
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") });
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] {new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22")});
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
@ -306,7 +308,7 @@ public class GenericClientTest {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK"));
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") });
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] {new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22")});
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
@ -353,7 +355,7 @@ public class GenericClientTest {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK"));
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") });
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] {new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22")});
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
@ -382,7 +384,7 @@ public class GenericClientTest {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK"));
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") });
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] {new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22")});
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
@ -406,7 +408,7 @@ public class GenericClientTest {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK"));
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") });
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] {new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22")});
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(ooStr), Charset.forName("UTF-8")));
@ -523,7 +525,7 @@ public class GenericClientTest {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") });
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] {new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22")});
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
@Override
@ -543,6 +545,37 @@ public class GenericClientTest {
assertEquals("http://example.com/fhir/Patient?name%3Amissing=false", capt.getValue().getRequestLine().getUri());
}
@Test
public void testProcessMessage() throws IOException {
Bundle respBundle = new Bundle();
respBundle.setType(BundleType.MESSAGE);
String respString = ourCtx.newJsonParser().encodeResourceToString(respBundle);
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")));
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[0]);
Bundle bundle = new Bundle();
bundle.setType(BundleType.MESSAGE);
Parameters parameters = new Parameters();
parameters.addParameter()
.setName("content")
.setResource(bundle);
int count = 0;
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
client.operation().onType(MessageHeader.class).named("$process-message").withParameters(parameters).execute();
assertEquals("http://example.com/fhir/MessageHeader/$process-message", capt.getAllValues().get(count).getURI().toString());
String requestContent = IOUtils.toString(((HttpPost) capt.getAllValues().get(count)).getEntity().getContent(), Charsets.UTF_8);
assertThat(requestContent, startsWith("<Parameters xmlns=\"http://hl7.org/fhir\">"));
count++;
}
@Test
public void testRead() throws Exception {
@ -633,7 +666,7 @@ public class GenericClientTest {
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
Header[] headers = new Header[] { new BasicHeader(Constants.HEADER_LAST_MODIFIED, "Wed, 15 Nov 1995 04:58:08 GMT"),
Header[] headers = new Header[] {new BasicHeader(Constants.HEADER_LAST_MODIFIED, "Wed, 15 Nov 1995 04:58:08 GMT"),
new BasicHeader(Constants.HEADER_CONTENT_LOCATION, "http://foo.com/Patient/123/_history/2333"),
};
when(myHttpResponse.getAllHeaders()).thenReturn(headers);
@ -1533,7 +1566,7 @@ public class GenericClientTest {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK"));
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") });
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] {new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22")});
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
@ -1594,7 +1627,7 @@ public class GenericClientTest {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK"));
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") });
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] {new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22")});
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));

View File

@ -1587,6 +1587,46 @@ public class AuthorizationInterceptorR4Test {
assertFalse(ourHitMethod);
}
@Test
public void testOperationTypeLevelDifferentBodyType() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("RULE 1").operation().named("process-message").onType(MessageHeader.class).andThen()
.build();
}
});
HttpPost httpPost;
HttpResponse status;
String response;
Bundle input = new Bundle();
input.setType(Bundle.BundleType.MESSAGE);
String inputString = ourCtx.newJsonParser().encodeResourceToString(input);
// With body
ourHitMethod = false;
httpPost = new HttpPost("http://localhost:" + ourPort + "/MessageHeader/$process-message");
httpPost.setEntity(new StringEntity(inputString, ContentType.create(Constants.CT_FHIR_JSON_NEW, Charsets.UTF_8)));
status = ourClient.execute(httpPost);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// With body
ourHitMethod = false;
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/MessageHeader/$process-message");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
}
@Test
public void testOperationWithTester() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@ -2946,12 +2986,13 @@ public class AuthorizationInterceptorR4Test {
DummyEncounterResourceProvider encProv = new DummyEncounterResourceProvider();
DummyCarePlanResourceProvider cpProv = new DummyCarePlanResourceProvider();
DummyDiagnosticReportResourceProvider drProv = new DummyDiagnosticReportResourceProvider();
DummyMessageHeaderResourceProvider mshProv = new DummyMessageHeaderResourceProvider();
PlainProvider plainProvider = new PlainProvider();
ServletHandler proxyHandler = new ServletHandler();
ourServlet = new RestfulServer(ourCtx);
ourServlet.setFhirContext(ourCtx);
ourServlet.setResourceProviders(patProvider, obsProv, encProv, cpProv, orgProv, drProv);
ourServlet.setResourceProviders(patProvider, obsProv, encProv, cpProv, orgProv, drProv, mshProv);
ourServlet.setPlainProviders(plainProvider);
ourServlet.setPagingProvider(new FifoMemoryPagingProvider(100));
ServletHolder servletHolder = new ServletHolder(ourServlet);
@ -3027,6 +3068,22 @@ public class AuthorizationInterceptorR4Test {
}
public static class DummyMessageHeaderResourceProvider implements IResourceProvider {
@Override
public Class<? extends IBaseResource> getResourceType() {
return MessageHeader.class;
}
@Operation(name = "process-message", idempotent = true)
public Parameters operation0(@OperationParam(name="content") Bundle theInput) {
ourHitMethod = true;
return (Parameters) new Parameters().setId("1");
}
}
public static class DummyDiagnosticReportResourceProvider implements IResourceProvider {

View File

@ -0,0 +1,251 @@
package ca.uhn.fhir.rest.server.interceptor;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Patient;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.*;
public class InjectionAttackTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(InjectionAttackTest.class);
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forR4();
private static int ourPort;
private static Server ourServer;
private static RestfulServer ourServlet;
@Test
public void testPreventHtmlInjectionViaInvalidContentType() throws Exception {
String requestUrl = "http://localhost:" +
ourPort +
"/Patient/123";
// XML HTML
HttpGet httpGet = new HttpGet(requestUrl);
httpGet.addHeader(Constants.HEADER_ACCEPT, "application/<script>");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, not(containsString("<script>")));
}
}
@Test
public void testPreventHtmlInjectionViaInvalidParameterName() throws Exception {
String requestUrl = "http://localhost:" +
ourPort +
"/Patient?a" +
UrlUtil.escapeUrlParam("<script>") +
"=123";
// XML HTML
HttpGet httpGet = new HttpGet(requestUrl);
httpGet.addHeader(Constants.HEADER_ACCEPT, Constants.CT_HTML + ", " + Constants.CT_FHIR_XML_NEW);
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(responseContent);
assertEquals(400, status.getStatusLine().getStatusCode());
assertThat(responseContent, not(containsString("<script>")));
assertEquals("text/html", status.getFirstHeader("Content-Type").getValue().toLowerCase().replaceAll(";.*", "").trim());
}
// JSON HTML
httpGet = new HttpGet(requestUrl);
httpGet.addHeader(Constants.HEADER_ACCEPT, Constants.CT_HTML + ", " + Constants.CT_FHIR_JSON_NEW);
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(responseContent);
assertEquals(400, status.getStatusLine().getStatusCode());
assertThat(responseContent, not(containsString("<script>")));
assertEquals("text/html", status.getFirstHeader("Content-Type").getValue().toLowerCase().replaceAll(";.*", "").trim());
}
// XML HTML
httpGet = new HttpGet(requestUrl);
httpGet.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_XML_NEW);
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(responseContent);
assertEquals(400, status.getStatusLine().getStatusCode());
assertThat(responseContent, not(containsString("<script>")));
assertEquals(Constants.CT_FHIR_XML_NEW, status.getFirstHeader("Content-Type").getValue().toLowerCase().replaceAll(";.*", "").trim());
}
// JSON Plain
httpGet = new HttpGet(requestUrl);
httpGet.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_JSON_NEW);
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(responseContent);
assertEquals(400, status.getStatusLine().getStatusCode());
assertThat(responseContent, not(containsString("<script>")));
assertEquals(Constants.CT_FHIR_JSON_NEW, status.getFirstHeader("Content-Type").getValue().toLowerCase().replaceAll(";.*", "").trim());
}
}
@Test
public void testPreventHtmlInjectionViaInvalidResourceType() throws Exception {
String requestUrl = "http://localhost:" +
ourPort +
"/AA" +
UrlUtil.escapeUrlParam("<script>");
// XML HTML
HttpGet httpGet = new HttpGet(requestUrl);
httpGet.addHeader(Constants.HEADER_ACCEPT, Constants.CT_HTML + ", " + Constants.CT_FHIR_XML_NEW);
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(responseContent);
assertEquals(404, status.getStatusLine().getStatusCode());
assertThat(responseContent, not(containsString("<script>")));
assertEquals("text/html", status.getFirstHeader("Content-Type").getValue().toLowerCase().replaceAll(";.*", "").trim());
}
// JSON HTML
httpGet = new HttpGet(requestUrl);
httpGet.addHeader(Constants.HEADER_ACCEPT, Constants.CT_HTML + ", " + Constants.CT_FHIR_JSON_NEW);
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(responseContent);
assertEquals(404, status.getStatusLine().getStatusCode());
assertThat(responseContent, not(containsString("<script>")));
assertEquals("text/html", status.getFirstHeader("Content-Type").getValue().toLowerCase().replaceAll(";.*", "").trim());
}
// XML HTML
httpGet = new HttpGet(requestUrl);
httpGet.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_XML_NEW);
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(responseContent);
assertEquals(404, status.getStatusLine().getStatusCode());
assertThat(responseContent, not(containsString("<script>")));
assertEquals(Constants.CT_FHIR_XML_NEW, status.getFirstHeader("Content-Type").getValue().toLowerCase().replaceAll(";.*", "").trim());
}
// JSON Plain
httpGet = new HttpGet(requestUrl);
httpGet.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_JSON_NEW);
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(responseContent);
assertEquals(404, status.getStatusLine().getStatusCode());
assertThat(responseContent, not(containsString("<script>")));
assertEquals(Constants.CT_FHIR_JSON_NEW, status.getFirstHeader("Content-Type").getValue().toLowerCase().replaceAll(";.*", "").trim());
}
}
@Test
public void testPreventHtmlInjectionViaInvalidTokenParamModifier() throws Exception {
String requestUrl = "http://localhost:" +
ourPort +
"/Patient?identifier:" +
UrlUtil.escapeUrlParam("<script>") +
"=123";
HttpGet httpGet = new HttpGet(requestUrl);
httpGet.addHeader(Constants.HEADER_ACCEPT, "application/<script>");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, not(containsString("<script>")));
}
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
ServletHandler proxyHandler = new ServletHandler();
ourServlet = new RestfulServer(ourCtx);
ourServlet.setFhirContext(ourCtx);
ourServlet.setResourceProviders(patientProvider);
ourServlet.registerInterceptor(new ResponseHighlighterInterceptor());
ServletHolder servletHolder = new ServletHolder(ourServlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
public static class DummyPatientResourceProvider implements IResourceProvider {
@Override
public Class<? extends Patient> getResourceType() {
return Patient.class;
}
@Read
public Patient read(@IdParam IdType theId) {
Patient patient = new Patient();
patient.setId(theId);
patient.setActive(true);
return patient;
}
@Search
public List<Patient> search(@OptionalParam(name = "identifier") TokenParam theToken) {
return new ArrayList<>();
}
}
}

View File

@ -1,11 +1,16 @@
package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.util.concurrent.TimeUnit;
package ca.uhn.fhir.rest.server.interceptor;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
@ -15,29 +20,21 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Patient;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.*;
public class ServerWithResponseHighlightingInterceptorExceptionTest {
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forDstu2();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerWithResponseHighlightingInterceptorExceptionTest.class);
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forR4();
private static int ourPort;
private static Server ourServer;
private static RestfulServer ourServlet;
@ -67,7 +64,6 @@ public class ServerWithResponseHighlightingInterceptorExceptionTest {
assertThat(responseContent, containsString("<diagnostics value=\"Failed to call access method: java.lang.Error: AAABBB\"/>"));
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
@ -102,20 +98,21 @@ public class ServerWithResponseHighlightingInterceptorExceptionTest {
public static class DummyPatientResourceProvider implements IResourceProvider {
@Override
public Class<? extends IResource> getResourceType() {
public Class<? extends Patient> getResourceType() {
return Patient.class;
}
@Read
public Patient read(@IdParam IdDt theId) {
public Patient read(@IdParam IdType theId) {
throw new InvalidRequestException("AAABBB");
}
@Search
public Patient search(@RequiredParam(name="identifier") TokenParam theToken) {
public Patient search(@RequiredParam(name = "identifier") TokenParam theToken) {
throw new Error("AAABBB");
}
}
}

View File

@ -22,7 +22,7 @@ import ca.uhn.fhir.rest.api.SortSpec;
public class ${className}ResourceProvider extends
## We have specialized base classes for RPs that handle certain resource types. These
## RPs implement type specific operations
#if ( $version != 'dstu' && (${className} == 'Encounter' || ${className} == 'Patient' || ${className} == 'Composition' || ${className} == 'ValueSet' || ${className} == 'QuestionnaireAnswers' || ${className} == 'CodeSystem' || ($version != 'dstu2' && ${className} == 'ConceptMap')))
#if ( $version != 'dstu' && (${className} == 'Encounter' || ${className} == 'Patient' || ${className} == 'ValueSet' || ${className} == 'QuestionnaireAnswers' || ${className} == 'CodeSystem' || ($version != 'dstu2' && ${className} == 'ConceptMap') || ${className} == 'MessageHeader' || ${className} == 'Composition'))
BaseJpaResourceProvider${className}${versionCapitalized}
#else
JpaResourceProvider${versionCapitalized}<${className}>

View File

@ -26,11 +26,20 @@ import ca.uhn.fhir.jpa.dao.*;
@Configuration
public abstract class BaseJavaConfig${versionCapitalized} extends ca.uhn.fhir.jpa.config${package_suffix}.Base${versionCapitalized}Config {
/**
* Subclasses may override
*/
protected boolean isSupported(String theResourceType) {
return true;
}
@Bean(name="myResourceProviders${versionCapitalized}")
public List<IResourceProvider> resourceProviders${versionCapitalized}() {
List<IResourceProvider> retVal = new ArrayList<IResourceProvider>();
#foreach ( $res in $resources )
if (isSupported("${res.name}")) {
retVal.add(rp${res.declaringClassNameComplete}${versionCapitalized}());
}
#end
return retVal;
}
@ -39,7 +48,9 @@ public abstract class BaseJavaConfig${versionCapitalized} extends ca.uhn.fhir.jp
public List<IFhirResourceDao<?>> resourceDaos${versionCapitalized}() {
List<IFhirResourceDao<?>> retVal = new ArrayList<IFhirResourceDao<?>>();
#foreach ( $res in $resources )
if (isSupported("${res.name}")) {
retVal.add(dao${res.declaringClassNameComplete}${versionCapitalized}());
}
#end
return retVal;
}
@ -62,7 +73,7 @@ public abstract class BaseJavaConfig${versionCapitalized} extends ca.uhn.fhir.jp
IFhirResourceDaoConceptMap<org.hl7.fhir.dstu3.model.ConceptMap>
#elseif ( ${versionCapitalized} == 'R4' && ${res.name} == 'ConceptMap' )
IFhirResourceDaoConceptMap<org.hl7.fhir.r4.model.ConceptMap>
#elseif ( ${versionCapitalized} != 'Dstu1' && ( ${res.name} == 'Composition' || ${res.name} == 'Encounter' || ${res.name} == 'Everything' || ${res.name} == 'Patient' || ${res.name} == 'Subscription' || ${res.name} == 'SearchParameter'))
#elseif ( ${versionCapitalized} != 'Dstu1' && (${res.name} == 'Composition' || ${res.name} == 'Encounter' || ${res.name} == 'Everything' || ${res.name} == 'Patient' || ${res.name} == 'Subscription' || ${res.name} == 'SearchParameter' || ${res.name} == 'MessageHeader'))
IFhirResourceDao${res.name}<${resourcePackage}.${res.declaringClassNameComplete}>
#else
IFhirResourceDao<${resourcePackage}.${res.declaringClassNameComplete}>
@ -72,7 +83,7 @@ public abstract class BaseJavaConfig${versionCapitalized} extends ca.uhn.fhir.jp
ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${res.name}${versionCapitalized} retVal = new ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${res.name}${versionCapitalized}();
#elseif ( ${versionCapitalized} == 'R4' && ${res.name} == 'ConceptMap' )
ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${res.name}${versionCapitalized} retVal = new ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${res.name}${versionCapitalized}();
#elseif ( ${versionCapitalized} != 'Dstu1' && ( ${res.name} == 'Bundle' || ${res.name} == 'Encounter' || ${res.name} == 'Everything' || ${res.name} == 'Composition' || ${res.name} == 'Patient' || ${res.name} == 'Subscription' || ${res.name} == 'ValueSet' || ${res.name} == 'QuestionnaireResponse' || ${res.name} == 'SearchParameter' || ${res.name} == 'CodeSystem'))
#elseif ( ${res.name} == 'Bundle' || ${res.name} == 'Encounter' || ${res.name} == 'Everything' || ${res.name} == 'Patient' || ${res.name} == 'Subscription' || ${res.name} == 'ValueSet' || ${res.name} == 'QuestionnaireResponse' || ${res.name} == 'SearchParameter' || ${res.name} == 'CodeSystem' || ${res.name} == 'MessageHeader' || ${res.name} == 'Composition')
ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${res.name}${versionCapitalized} retVal = new ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${res.name}${versionCapitalized}();
#else
ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${versionCapitalized}<${resourcePackage}.${res.declaringClassNameComplete}> retVal = new ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${versionCapitalized}<${resourcePackage}.${res.declaringClassNameComplete}>();

View File

@ -138,6 +138,18 @@
A crash in the JPA server when performing a manual reindex of a deleted resource
was fixed.
</action>
<action type="fix">
Using the generic/fluent client, it is now possible to
invoke the $process-message method using a standard
client.operation() call. Previously this caused a strange
NullPointerException.
</action>
<action type="fix">
The REST Server now sanitizes URL path components and query parameter
names to escape several reserved characters (e.g. &quot; and &lt;)
in order to prevent HTML injection attacks via maliciously
crafted URLs.
</action>
</release>
<release version="3.4.0" date="2018-05-28">
<action type="add">