diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000000..3829daa7291 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -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. diff --git a/README.md b/README.md index ad66edaa9d7..d753df33492 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java index 01ccc3ca7b8..86514b5ae53 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java @@ -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,30 +50,10 @@ 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.). - * + * *

* Important usage notes: *

@@ -68,6 +75,7 @@ public class FhirContext { private static final List> 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, BaseRuntimeElementDefinition> myClassToElementDefinition = Collections.emptyMap(); private ArrayList> myCustomTypes; @@ -87,14 +95,11 @@ public class FhirContext { private volatile IRestfulClientFactory myRestfulClientFactory; private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition; private IContextValidationSupport myValidationSupport; - - private final IFhirVersion myVersion; - private Map>> myVersionToNameToResourceType = Collections.emptyMap(); /** * @deprecated It is recommended that you use one of the static initializer methods instead - * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()} + * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()} */ @Deprecated public FhirContext() { @@ -103,7 +108,7 @@ public class FhirContext { /** * @deprecated It is recommended that you use one of the static initializer methods instead - * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()} + * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()} */ @Deprecated public FhirContext(Class theResourceType) { @@ -112,7 +117,7 @@ public class FhirContext { /** * @deprecated It is recommended that you use one of the static initializer methods instead - * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()} + * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()} */ @Deprecated public FhirContext(Class... theResourceTypes) { @@ -121,7 +126,7 @@ public class FhirContext { /** * @deprecated It is recommended that you use one of the static initializer methods instead - * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()} + * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()} */ @Deprecated public FhirContext(Collection> theResourceTypes) { @@ -161,7 +166,7 @@ public class FhirContext { if (theVersion == null) { ourLog.info("Creating new FhirContext with auto-detected version [{}]. It is recommended to explicitly select a version for future compatibility by invoking FhirContext.forDstuX()", - myVersion.getVersion().name()); + myVersion.getVersion().name()); } else { ourLog.info("Creating new FHIR context for FHIR version [{}]", myVersion.getVersion().name()); } @@ -201,13 +206,37 @@ public class FhirContext { * 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}. - * + * * @see #setAddProfileTagWhenEncoding(AddProfileTagEnum) for more information */ public AddProfileTagEnum getAddProfileTagWhenEncoding() { 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}. + *

+ * 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. + *

+ *

+ * See Profiling and Extensions + * for more information on using custom types. + *

+ *

+ * Note that this feature automatically adds the profile, but leaves any profile tags + * which have been manually added in place as well. + *

+ * + * @param theAddProfileTagWhenEncoding The add profile mode (must not be null) + */ + public void setAddProfileTagWhenEncoding(AddProfileTagEnum theAddProfileTagWhenEncoding) { + Validate.notNull(theAddProfileTagWhenEncoding, "theAddProfileTagWhenEncoding must not be null"); + myAddProfileTagWhenEncoding = theAddProfileTagWhenEncoding; + } + Collection getAllResourceDefinitions() { validateInitialized(); return myNameToResourceDefinition.values(); @@ -215,7 +244,7 @@ public class FhirContext { /** * Returns the default resource type for the given profile - * + * * @see #setDefaultTypeForProfile(String, Class) */ public Class getDefaultTypeForProfile(String theProfile) { @@ -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,20 +305,43 @@ 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 - * + * * @return The parser options - Will not return null */ public ParserOptions getParserOptions() { 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 null + */ + 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 getResourceDefinitions() { + // + // Set> datatypes = Collections.emptySet(); + // Map, BaseRuntimeElementDefinition> existing = Collections.emptyMap(); + // HashMap> types = new HashMap>(); + // 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 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 { *

* Note that this method is case insensitive! *

+ * + * @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 getResourceDefinitions() { - // - // Set> datatypes = Collections.emptySet(); - // Map, BaseRuntimeElementDefinition> existing = Collections.emptyMap(); - // HashMap> types = new HashMap>(); - // 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,10 +482,40 @@ public class FhirContext { return myIdToResourceDefinition.values(); } + /** + * Returns an unmodifiable set containing all resource names known to this + * context + */ + public Set getResourceNames() { + Set 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. - * + * * @return the factory used to create the restful clients */ public IRestfulClientFactory getRestfulClientFactory() { @@ -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; @@ -438,7 +548,7 @@ public class FhirContext { * Returns the validation support module configured for this context, creating a default * implementation if no module has been passed in via the {@link #setValidationSupport(IContextValidationSupport)} * method - * + * * @see #setValidationSupport(IContextValidationSupport) */ public IContextValidationSupport getValidationSupport() { @@ -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; } @@ -455,7 +574,7 @@ public class FhirContext { /** * Returns true if any default types for specific profiles have been defined * within this context. - * + * * @see #setDefaultTypeForProfile(String, Class) * @see #getDefaultTypeForProfile(String) */ @@ -483,7 +602,7 @@ public class FhirContext { * on a context for a previous version of fhir will result in an * {@link UnsupportedOperationException} *

- * + * * @since 2.2 */ public IFluentPath newFluentPath() { @@ -492,7 +611,7 @@ public class FhirContext { /** * Create and return a new JSON parser. - * + * *

* Thread safety: Parsers are not guaranteed to be thread safe. Create a new parser instance for every thread * or every message being parsed/encoded. @@ -513,19 +632,16 @@ public class FhirContext { * sub-interface {@link IBasicClient}). See the RESTful Client documentation for more * information on how to define this interface. - * + * *

* Performance Note: This method is cheap to call, and may be called once for every operation invocation * without incurring any performance penalty *

- * - * @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 newRestfulClient(Class theClientType, String theServerBase) { return getRestfulClientFactory().newClient(theClientType, theServerBase); @@ -535,14 +651,13 @@ public class FhirContext { * Instantiates a new generic client. A generic client is able to perform any of the FHIR RESTful operations against * a compliant server, but does not have methods defining the specific functionality required (as is the case with * {@link #newRestfulClient(Class, String) non-generic clients}). - * + * *

* Performance Note: This method is cheap to call, and may be called once for every operation invocation * without incurring any performance penalty *

- * - * @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); @@ -569,7 +684,7 @@ public class FhirContext { /** * Create and return a new XML parser. - * + * *

* Thread safety: Parsers are not guaranteed to be thread safe. Create a new parser instance for every thread * or every message being parsed/encoded. @@ -592,9 +707,8 @@ public class FhirContext { * THREAD SAFETY WARNING: This method is not thread safe. It should be called before any * threads are able to call any methods on this context. *

- * - * @param theType - * The custom type to add (must not be null) + * + * @param theType The custom type to add (must not be null) */ public void registerCustomType(Class theType) { Validate.notNull(theType, "theType must not be null"); @@ -612,9 +726,8 @@ public class FhirContext { * THREAD SAFETY WARNING: This method is not thread safe. It should be called before any * threads are able to call any methods on this context. *

- * - * @param theTypes - * The custom types to add (must not be null or contain null elements in the collection) + * + * @param theTypes The custom types to add (must not be null or contain null elements in the collection) */ public void registerCustomTypes(Collection> 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}. - *

- * 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. - *

- *

- * See Profiling and Extensions - * for more information on using custom types. - *

- *

- * Note that this feature automatically adds the profile, but leaves any profile tags - * which have been manually added in place as well. - *

- * - * @param theAddProfileTagWhenEncoding - * The add profile mode (must not be null) - */ - 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. @@ -732,12 +820,10 @@ public class FhirContext { * if the parser is parsing a resource and finds that it declares that it conforms to that profile, * the MyPatient type will be used unless otherwise specified. *

- * - * @param theProfile - * The profile string, e.g. "http://example.com/some_patient_profile". Must not be - * null or empty. - * @param theClass - * The resource type, or null to clear any existing type + * + * @param theProfile The profile string, e.g. "http://example.com/some_patient_profile". Must not be + * null or empty. + * @param theClass The resource type, or null to clear any existing type */ public void setDefaultTypeForProfile(String theProfile, Class theClass) { Validate.notBlank(theProfile, "theProfile must not be null or empty"); @@ -748,56 +834,19 @@ 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 null - */ - 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 theOptions) { - myPerformanceOptions.clear(); - if (theOptions != null) { - myPerformanceOptions.addAll(theOptions); - } - } - - /** - * Sets the configured performance options - * + * * @see PerformanceOptionsEnum for a list of available options */ public void setPerformanceOptions(PerformanceOptionsEnum... thePerformanceOptions) { @@ -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> toElementList(Collection> 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,9 +896,16 @@ 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} - * + * * @since 1.4 */ public static FhirContext forDstu3() { @@ -884,14 +914,13 @@ public class FhirContext { /** * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU3 DSTU3} - * + * * @since 3.0.0 */ public static FhirContext forR4() { return new FhirContext(FhirVersionEnum.R4); } - private static Collection> toCollection(Class theResourceType) { ArrayList> retVal = new ArrayList>(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 getResourceNames() { - Set 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); - } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlPathTokenizer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlPathTokenizer.java index 0e60d47a30f..7c7a99f2915 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlPathTokenizer.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlPathTokenizer.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.util; * 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. @@ -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 < and " 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())); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlUtil.java index 627b6efefa6..5061148b7c1 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlUtil.java @@ -25,9 +25,9 @@ import static org.apache.commons.lang3.StringUtils.isBlank; * 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. @@ -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 parseQueryString(String theQueryString) { - HashMap> map = new HashMap>(); + HashMap> map = new HashMap<>(); parseQueryString(theQueryString, map); return toQueryStringMap(map); } @@ -197,17 +209,13 @@ public class UrlUtil { nextKey = unescape(nextKey); nextValue = unescape(nextValue); - List list = map.get(nextKey); - if (list == null) { - list = new ArrayList<>(); - map.put(nextKey, list); - } + List list = map.computeIfAbsent(nextKey, k -> new ArrayList<>()); list.add(nextValue); } } public static Map parseQueryStrings(String... theQueryString) { - HashMap> map = new HashMap>(); + HashMap> map = new HashMap<>(); for (String next : theQueryString) { parseQueryString(next, map); } @@ -222,7 +230,6 @@ public class UrlUtil { *
  • [Resource Type]/[Resource ID]/_history/[Version ID] * */ - //@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 " and + * < 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("""); + break; + case '<': + buffer.append("<"); + break; + default: + buffer.append(nextChar); + break; + } + + } // for build escaped string + + return buffer.toString(); + } + + return theString; + } private static Map toQueryStringMap(HashMap> map) { - HashMap retVal = new HashMap(); + HashMap retVal = new HashMap<>(); for (Entry> 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; } diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on.xml b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on.xml index 9a96c590af6..730679bd5bb 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on.xml @@ -31,6 +31,13 @@ + + + + + diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java index 5dd55c8bf31..000e0eb2a48 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java @@ -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> urlParams = new LinkedHashMap>(); // Set Url parameter Async and Response-Url if (myIsAsync != null) { diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseHttpClientInvocationWithContents.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseHttpClientInvocationWithContents.java index 8dc61aa7d0d..86724743f68 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseHttpClientInvocationWithContents.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseHttpClientInvocationWithContents.java @@ -57,17 +57,6 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca private IIdType myForceResourceId; - - public BaseHttpClientInvocationWithContents(FhirContext theContext, IBaseResource theResource, Map> 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> 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> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) throws DataFormatException { StringBuilder url = new StringBuilder(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index 4d9d0af6c3b..4b643375b78 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -1167,7 +1167,7 @@ public abstract class BaseHapiFhirDao implements IDao, public SearchBuilder newSearchBuilder() { SearchBuilder builder = new SearchBuilder( getContext(), myEntityManager, myFulltextSearchSvc, this, myResourceIndexedSearchParamUriDao, - myForcedIdDao, myTerminologySvc, mySerarchParamRegistry, myResourceTagDao, myResourceViewDao); + myForcedIdDao, myTerminologySvc, mySerarchParamRegistry, myResourceTagDao, myResourceViewDao); return builder; } @@ -1301,20 +1301,24 @@ public abstract class BaseHapiFhirDao implements IDao, } } - // Don't keep duplicate tags + Set allTagsNew = getAllTagDefinitions(theEntity); Set allDefsPresent = new HashSet<>(); - theEntity.getTags().removeIf(theResourceTag -> !allDefsPresent.add(theResourceTag.getTag())); + allTagsNew.forEach(tag -> { - // Remove any tags that have been removed - for (ResourceTag next : allTagsOld) { - if (!allDefs.contains(next)) { - if (shouldDroppedTagBeRemovedOnUpdate(theRequest, next)) { - theEntity.getTags().remove(next); + // 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); } } - } - Set allTagsNew = getAllTagDefinitions(theEntity); + }); + if (!allTagsOld.equals(allTagsNew)) { changed = true; } @@ -1473,6 +1477,15 @@ public abstract class BaseHapiFhirDao 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 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 * @@ -1626,20 +1630,20 @@ public abstract class BaseHapiFhirDao implements IDao, @SuppressWarnings("unchecked") @Override public R toResource(Class theResourceType, IBaseResourceEntity theEntity, Collection theTagList, boolean theForHistoryOperation) { - + // 1. get resource, it's encoding and the tags if any byte[] resourceBytes = null; ResourceEncodingEnum resourceEncoding = null; Collection myTagList = null; - + if (theEntity instanceof ResourceHistoryTable) { ResourceHistoryTable history = (ResourceHistoryTable) theEntity; resourceBytes = history.getResource(); resourceEncoding = history.getEncoding(); myTagList = history.getTags(); } else if (theEntity instanceof ResourceTable) { - ResourceTable resource = (ResourceTable)theEntity; - ResourceHistoryTable history = myResourceHistoryTableDao.findForIdAndVersion(theEntity.getId(), theEntity.getVersion()); + 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 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) @@ -1663,7 +1667,7 @@ public abstract class BaseHapiFhirDao implements IDao, // 2. get The text String resourceText = null; switch (resourceEncoding) { - case JSON: + case JSON: try { resourceText = new String(resourceBytes, "UTF-8"); } catch (UnsupportedEncodingException e) { @@ -1676,7 +1680,7 @@ public abstract class BaseHapiFhirDao implements IDao, case DEL: break; } - + // 3. Use the appropriate custom type if one is specified in the context Class resourceType = theResourceType; if (myContext.hasDefaultTypeForProfile()) { @@ -2046,6 +2050,7 @@ public abstract class BaseHapiFhirDao 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 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 implements IDao, } // if thePerformIndexing - theEntity = myEntityManager.merge(theEntity); - if (theResource != null) { populateResourceIdFromEntity(theEntity, theResource); } + return theEntity; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 411ea977d39..b8a5df18c3f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.dao; * 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. @@ -395,16 +395,6 @@ public abstract class BaseHapiFhirResourceDao 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 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(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoMessageHeaderDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoMessageHeaderDstu2.java new file mode 100644 index 00000000000..19c1c627d0e --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoMessageHeaderDstu2.java @@ -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 implements IFhirResourceDaoMessageHeader { + + @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"); + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoMessageHeader.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoMessageHeader.java new file mode 100644 index 00000000000..1bbc494b96c --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoMessageHeader.java @@ -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 extends IFhirResourceDao { + + IBaseBundle messageHeaderProcessMessage(RequestDetails theRequestDetails, IBaseBundle theMessage); + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDao.java index 23b3910c3b1..d0f519b2729 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDao.java @@ -21,9 +21,9 @@ import java.util.List; * 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. @@ -34,15 +34,18 @@ import java.util.List; public interface ITermConceptDao extends JpaRepository { + @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 findByCodeSystemVersion(@Param("code_system") TermCodeSystemVersion theCodeSystem); - @Query("SELECT t FROM TermConcept t WHERE t.myCodeSystem.myId = :cs_pid") Slice findByCodeSystemVersion(Pageable thePage, @Param("cs_pid") Long thePid); + @Query("SELECT c FROM TermConcept c WHERE c.myCodeSystem = :code_system") + List findByCodeSystemVersion(@Param("code_system") TermCodeSystemVersion theCodeSystem); + @Query("SELECT t FROM TermConcept t WHERE t.myIndexStatus = null") Page findResourcesRequiringReindexing(Pageable thePageRequest); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDesignationDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDesignationDao.java index 8b90ab5a673..38c8346b790 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDesignationDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDesignationDao.java @@ -32,4 +32,7 @@ public interface ITermConceptDesignationDao extends JpaRepository 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); + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptParentChildLinkDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptParentChildLinkDao.java index 3b7d25d1feb..d3cb23d9896 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptParentChildLinkDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptParentChildLinkDao.java @@ -31,6 +31,9 @@ import java.util.Collection; public interface ITermConceptParentChildLinkDao extends JpaRepository { + @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 findAllWithChild(@Param("child_pid") Long theConceptPid); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptPropertyDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptPropertyDao.java index 446e426e340..37d276e6e1c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptPropertyDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptPropertyDao.java @@ -32,4 +32,6 @@ public interface ITermConceptPropertyDao extends JpaRepository 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); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoMessageHeaderDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoMessageHeaderDstu3.java new file mode 100644 index 00000000000..5be5bb25661 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoMessageHeaderDstu3.java @@ -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 implements IFhirResourceDaoMessageHeader { + + @Override + public IBaseBundle messageHeaderProcessMessage(RequestDetails theRequestDetails, IBaseBundle theMessage) { + return FhirResourceDaoMessageHeaderDstu2.throwProcessMessageNotImplemented(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java index 23397616bc0..2caab674724 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java @@ -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 if (vs != null) { ValueSet expansion = doExpand(vs); List 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 } + + private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoValueSetDstu3.class); + private String toStringOrNull(IPrimitiveType thePrimitive) { return thePrimitive != null ? thePrimitive.getValue() : null; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoMessageHeaderR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoMessageHeaderR4.java new file mode 100644 index 00000000000..ba8ce1d8ded --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoMessageHeaderR4.java @@ -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 implements IFhirResourceDaoMessageHeader { + + @Override + public IBaseBundle messageHeaderProcessMessage(RequestDetails theRequestDetails, IBaseBundle theMessage) { + return FhirResourceDaoMessageHeaderDstu2.throwProcessMessageNotImplemented(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4.java index 01fde26c695..53c559b446f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4.java @@ -187,7 +187,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements */ @Override public HashSet extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) { - HashSet retVal = new HashSet(); + HashSet retVal = new HashSet<>(); Collection searchParams = getSearchParams(theResource); for (RuntimeSearchParam nextSpDef : searchParams) { @@ -290,7 +290,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements */ @Override public Set extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) { - HashSet retVal = new HashSet(); + HashSet retVal = new HashSet<>(); Collection searchParams = getSearchParams(theResource); for (RuntimeSearchParam nextSpDef : searchParams) { @@ -354,7 +354,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements */ @Override public Set extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) { - HashSet retVal = new HashSet(); + HashSet 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 allNames = new ArrayList(); + ArrayList 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 allNames = new ArrayList(); + ArrayList 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> haveValues = new HashSet>(); + Set> 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 extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) { - HashSet retVal = new HashSet(); + HashSet retVal = new HashSet<>(); Collection 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 values = new ArrayList(); + List 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 extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) { - ArrayList retVal = new ArrayList(); + ArrayList retVal = new ArrayList<>(); String[] nextPathsSplit = SPLIT.split(theNextSpDef.getPath()); for (String path : nextPathsSplit) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java index 770544375c2..8d8d270471c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java @@ -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 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; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ForcedId.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ForcedId.java index a9cb05ef306..0c451533c70 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ForcedId.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ForcedId.java @@ -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; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamToken.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamToken.java index a1bea4a835e..75938fb79e8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamToken.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamToken.java @@ -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; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java index a715cc02679..b672aca316d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java @@ -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 myProperties; - @OneToMany(mappedBy = "myConcept", orphanRemoval = false) private Collection 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 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(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderMessageHeaderDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderMessageHeaderDstu2.java new file mode 100644 index 00000000000..c4eaffa40f0 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderMessageHeaderDstu2.java @@ -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/$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) getDao()).messageHeaderProcessMessage(theRequestDetails, theMessageToProcess); + } finally { + endRequest(theServletRequest); + } + + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderMessageHeaderDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderMessageHeaderDstu3.java new file mode 100644 index 00000000000..ee23124e60e --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderMessageHeaderDstu3.java @@ -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/$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) getDao()).messageHeaderProcessMessage(theRequestDetails, theMessageToProcess); + } finally { + endRequest(theServletRequest); + } + + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderMessageHeaderR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderMessageHeaderR4.java new file mode 100644 index 00000000000..6934d9e033e --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderMessageHeaderR4.java @@ -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/$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) getDao()).messageHeaderProcessMessage(theRequestDetails, theMessageToProcess); + } finally { + endRequest(theServletRequest); + } + + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/LuceneSearchMappingFactory.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/LuceneSearchMappingFactory.java index d316902a73c..e94da64629a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/LuceneSearchMappingFactory.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/LuceneSearchMappingFactory.java @@ -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); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java index 2f331777292..61d6f45cc8e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java @@ -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 link = myConceptParentChildLinkDao.findByCodeSystemVersion(page, theCodeSystemVersionPid); - if (link.hasContent() == false) { - break; - } - - myConceptParentChildLinkDao.deleteInBatch(link); - - count += link.getNumberOfElements(); - ourLog.info(" * {} parent/child links deleted", count); + { + String descriptor = "parent/child links"; + Supplier> loader = () -> myConceptParentChildLinkDao.findByCodeSystemVersion(page1000, theCodeSystemVersionPid); + Supplier counter = () -> myConceptParentChildLinkDao.countByCodeSystemVersion(theCodeSystemVersionPid); + doDelete(descriptor, loader, counter, myConceptParentChildLinkDao); } - myConceptParentChildLinkDao.flush(); // Properties - ourLog.info(" * Deleting properties"); - count = 0; - while (true) { - Slice link = myConceptPropertyDao.findByCodeSystemVersion(page, theCodeSystemVersionPid); - if (link.hasContent() == false) { - break; - } - - myConceptPropertyDao.deleteInBatch(link); - - count += link.getNumberOfElements(); - ourLog.info(" * {} concept properties deleted", count); + { + String descriptor = "concept properties"; + Supplier> loader = () -> myConceptPropertyDao.findByCodeSystemVersion(page1000, theCodeSystemVersionPid); + Supplier counter = () -> myConceptPropertyDao.countByCodeSystemVersion(theCodeSystemVersionPid); + doDelete(descriptor, loader, counter, myConceptPropertyDao); } - myConceptPropertyDao.flush(); - // Properties - ourLog.info(" * Deleting designations"); - count = 0; - while (true) { - Slice link = myConceptDesignationDao.findByCodeSystemVersion(page, theCodeSystemVersionPid); - if (link.hasContent() == false) { - break; - } - - myConceptDesignationDao.deleteInBatch(link); - - count += link.getNumberOfElements(); - ourLog.info(" * {} concept designations deleted", count); + // Designations + { + String descriptor = "concept designations"; + Supplier> loader = () -> myConceptDesignationDao.findByCodeSystemVersion(page1000, theCodeSystemVersionPid); + Supplier counter = () -> myConceptDesignationDao.countByCodeSystemVersion(theCodeSystemVersionPid); + doDelete(descriptor, loader, counter, myConceptDesignationDao); } - myConceptDesignationDao.flush(); // Concepts - ourLog.info(" * Deleting concepts"); - count = 0; - while (true) { - Slice 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> loader = () -> myConceptDao.findByCodeSystemVersion(page100, theCodeSystemVersionPid); + Supplier counter = () -> myConceptDao.countByCodeSystemVersion(theCodeSystemVersionPid); + doDelete(descriptor, loader, counter, myConceptDao); } Optional codeSystemOpt = myCodeSystemDao.findWithCodeSystemVersionAsCurrentVersion(theCodeSystemVersionPid); @@ -397,6 +369,26 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, deleteConceptMap(theResourceTable); } + private void doDelete(String theDescriptor, Supplier> theLoader, Supplier theCounter, JpaRepository theDao) { + int count; + ourLog.info(" * Deleting {}", theDescriptor); + int totalCount = theCounter.get(); + StopWatch sw = new StopWatch(); + count = 0; + while (true) { + Slice 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 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 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) { - StringBuilder parentsBuilder = new StringBuilder(); - createParentsString(parentsBuilder, nextConcept.getId()); - nextConcept.setParentPids(parentsBuilder.toString()); + 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()) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java index 9bdbb148e7b..aef6a45e4bc 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.term.loinc; * 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. diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JpaConstants.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JpaConstants.java index e5567524b05..812d63250ad 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JpaConstants.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JpaConstants.java @@ -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 */ diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java index 3de0c6202fe..21edfb9c3d5 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java @@ -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); + } + } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java index a7d9cccfe12..eb692759bcd 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java @@ -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); - } - } - } - } - } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java index c1152a7018a..5557e57e067 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java @@ -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; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/UnregisterScheduledProcessor.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/UnregisterScheduledProcessor.java new file mode 100644 index 00000000000..cf5943b37d3 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/UnregisterScheduledProcessor.java @@ -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(); + } + } + + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java index 2fe5f229028..0375a20d664 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java @@ -104,6 +104,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { @Qualifier("myConceptMapDaoR4") protected IFhirResourceDaoConceptMap myConceptMapDao; @Autowired + protected ITermConceptDao myTermConceptDao; + @Autowired @Qualifier("myConditionDaoR4") protected IFhirResourceDao myConditionDao; @Autowired diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java new file mode 100644 index 00000000000..0d6aebf7375 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java @@ -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(); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java index f43abf9f44c..bce25c41bbf 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java @@ -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,20 +17,36 @@ 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; @After public void after() { myDaoConfig.setDeferIndexingForCodesystemsOfSize(new DaoConfig().getDeferIndexingForCodesystemsOfSize()); - + BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); } @@ -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; } @@ -153,17 +121,48 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { TermConcept goodbye = new TermConcept(cs, "goodbye").setDisplay("Goodbye"); cs.getConcepts().add(goodbye); - + TermConcept dogs = new TermConcept(cs, "dogs").setDisplay("Dogs"); cs.getConcepts().add(dogs); - + TermConcept labrador = new TermConcept(cs, "labrador").setDisplay("Labrador"); dogs.addChild(labrador, RelationshipTypeEnum.ISA); 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; } @@ -171,17 +170,17 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { //@formatter:off CodeSystem codeSystem = new CodeSystem(); codeSystem.setUrl(URL_MY_CODE_SYSTEM); - codeSystem.setContent(CodeSystemContentMode.COMPLETE); + codeSystem.setContent(CodeSystemContentMode.COMPLETE); codeSystem .addConcept().setCode("A").setDisplay("Code A") - .addConcept(new ConceptDefinitionComponent().setCode("AA").setDisplay("Code AA") - .addConcept(new ConceptDefinitionComponent().setCode("AAA").setDisplay("Code AAA")) - ) - .addConcept(new ConceptDefinitionComponent().setCode("AB").setDisplay("Code AB")); + .addConcept(new ConceptDefinitionComponent().setCode("AA").setDisplay("Code AA") + .addConcept(new ConceptDefinitionComponent().setCode("AAA").setDisplay("Code AAA")) + ) + .addConcept(new ConceptDefinitionComponent().setCode("AB").setDisplay("Code AB")); codeSystem .addConcept().setCode("B").setDisplay("Code B") - .addConcept(new ConceptDefinitionComponent().setCode("BA").setDisplay("Code BA")) - .addConcept(new ConceptDefinitionComponent().setCode("BB").setDisplay("Code BB")); + .addConcept(new ConceptDefinitionComponent().setCode("BA").setDisplay("Code BA")) + .addConcept(new ConceptDefinitionComponent().setCode("BB").setDisplay("Code BB")); //@formatter:on myCodeSystemDao.create(codeSystem, mySrd); @@ -234,15 +233,15 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { //@formatter:off CodeSystem codeSystem = new CodeSystem(); codeSystem.setUrl(URL_MY_CODE_SYSTEM); - codeSystem.setContent(CodeSystemContentMode.COMPLETE); + codeSystem.setContent(CodeSystemContentMode.COMPLETE); codeSystem .addConcept().setCode("A").setDisplay("Code A") - .addConcept(new ConceptDefinitionComponent().setCode("AA").setDisplay("Code AA")) - .addConcept(new ConceptDefinitionComponent().setCode("AB").setDisplay("Code AB")); + .addConcept(new ConceptDefinitionComponent().setCode("AA").setDisplay("Code AA")) + .addConcept(new ConceptDefinitionComponent().setCode("AB").setDisplay("Code AB")); codeSystem .addConcept().setCode("B").setDisplay("Code A") - .addConcept(new ConceptDefinitionComponent().setCode("BA").setDisplay("Code AA")) - .addConcept(new ConceptDefinitionComponent().setCode("BB").setDisplay("Code AB")); + .addConcept(new ConceptDefinitionComponent().setCode("BA").setDisplay("Code AA")) + .addConcept(new ConceptDefinitionComponent().setCode("BB").setDisplay("Code AB")); //@formatter:on IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified(); @@ -252,6 +251,20 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { } + @Test + public void testConceptTimestamps() { + long start = System.currentTimeMillis() - 10; + + createExternalCsDogs(); + + runInTransaction(() -> { + List concepts = myTermConceptDao.findAll(); + for (TermConcept next : concepts) { + assertTrue(next.getUpdated().getTime() > start); + } + }); + } + @Test public void testExpandInvalid() { createExternalCsAndLocalVs(); @@ -278,17 +291,17 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { valueSet.setUrl(URL_MY_VALUE_SET); valueSet.getCompose() .addInclude() - .setSystem(codeSystem.getUrl()) - .addConcept(new ConceptReferenceComponent().setCode("hello")) - .addConcept(new ConceptReferenceComponent().setCode("goodbye")); + .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"); - + .setSystem(codeSystem.getUrl()) + .addFilter() + .setProperty("concept") + .setOp(FilterOperator.ISA) + .setValue("dogs"); + myValueSetDao.create(valueSet, mySrd); ValueSet result = myValueSetDao.expand(valueSet, ""); @@ -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 codes = toCodesContains(result.getExpansion().getContains()); - assertThat(codes, containsInAnyOrder("labrador")); - - } - @Test public void testExpandWithCodesAndDisplayFilterPartialOnCodes() { CodeSystem codeSystem = createExternalCsDogs(); @@ -349,17 +321,17 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { valueSet.setUrl(URL_MY_VALUE_SET); valueSet.getCompose() .addInclude() - .setSystem(codeSystem.getUrl()) - .addConcept(new ConceptReferenceComponent().setCode("hello")) - .addConcept(new ConceptReferenceComponent().setCode("goodbye")); + .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"); - + .setSystem(codeSystem.getUrl()) + .addFilter() + .setProperty("concept") + .setOp(FilterOperator.ISA) + .setValue("dogs"); + myValueSetDao.create(valueSet, mySrd); ValueSet result = myValueSetDao.expand(valueSet, "hel"); @@ -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 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 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(); @@ -484,7 +536,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { @Test public void testExpandWithIsAInExternalValueSetReindex() { BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); - + createExternalCsAndLocalVs(); mySystemDao.markAllResourcesForReindexing(); @@ -494,7 +546,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { myHapiTerminologySvc.saveDeferred(); myHapiTerminologySvc.saveDeferred(); myHapiTerminologySvc.saveDeferred(); - + ValueSet vs = new ValueSet(); ConceptSetComponent include = vs.getCompose().addInclude(); include.setSystem(URL_MY_CODE_SYSTEM); @@ -542,7 +594,17 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { assertEquals("unable to find code system http://example.com/my_code_systemAA", e.getMessage()); } } - + + // 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(); @@ -555,12 +617,12 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { include.addFilter().setProperty("display").setOp(FilterOperator.EQUAL).setValue("AAA"); ValueSet result = myValueSetDao.expand(vs, null); - + // Technically it's not valid to expand a ValueSet with both includes and filters so the // result fails validation because of the input.. we're being permissive by allowing both // though, so we won't validate the input result.setCompose(new ValueSetComposeComponent()); - + logAndValidateValueSet(result); ArrayList codes = toCodesContains(result.getExpansion().getContains()); @@ -631,7 +693,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { valueSet.setUrl(URL_MY_VALUE_SET); valueSet.getCompose() .addInclude() - .setSystem(codeSystem.getUrl()); + .setSystem(codeSystem.getUrl()); ValueSet result = myValueSetDao.expand(valueSet, ""); logAndValidateValueSet(result); @@ -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"); @@ -769,7 +831,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { myTermSvc.saveDeferred(); mySystemDao.performReindexingPass(null); myTermSvc.saveDeferred(); - + // Again mySystemDao.markAllResourcesForReindexing(); mySystemDao.performReindexingPass(null); @@ -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(); @@ -1019,7 +1067,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { SearchParameterMap params; ourLog.info("testSearchCodeInEmptyValueSet without status"); - + params = new SearchParameterMap(); params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); @@ -1030,7 +1078,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); params.add(Observation.SP_STATUS, new TokenParam(null, "final")); assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); - + ourLog.info("testSearchCodeInEmptyValueSet done"); } @@ -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(); @@ -1126,12 +1186,12 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { SearchParameterMap params; ourLog.info("testSearchCodeInEmptyValueSet without status"); - + params = new SearchParameterMap(); 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()); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTagSnapshotTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTagSnapshotTest.java index 632dd8bfa86..9f727ea1aa4 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTagSnapshotTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTagSnapshotTest.java @@ -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 diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java index cb7b0b034c7..64664d9e64d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java @@ -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); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java index 289f63e8f3e..c99071c9288 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java @@ -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,11 +85,11 @@ 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()); - assertEquals(200, resp.getStatusLine().getStatusCode()); + try (CloseableHttpResponse resp = ourHttpClient.execute(get)) { + assertEquals(200, resp.getStatusLine().getStatusCode()); + } } /** @@ -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 = ""; + + 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 = ""; - - 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 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(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3BundleTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3BundleTest.java index a7280d1094f..1f6eca2bf6d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3BundleTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3BundleTest.java @@ -1,46 +1,68 @@ 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); + /** + * See #401 + */ + @Test + public void testBundlePreservesFullUrl() { + + Bundle bundle = new Bundle(); + bundle.setType(BundleType.DOCUMENT); + + Composition composition = new Composition(); + composition.setTitle("Visit Summary"); + bundle.addEntry().setFullUrl("http://foo").setResource(composition); + + IIdType id = ourClient.create().resource(bundle).execute().getId(); + + 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(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(); } - /** - * See #401 - */ - @Test - public void testBundlePreservesFullUrl() throws Exception { - - Bundle bundle = new Bundle(); - bundle.setType(BundleType.DOCUMENT); - - Composition composition = new Composition(); - composition.setTitle("Visit Summary"); - bundle.addEntry().setFullUrl("http://foo").setResource(composition); - - IIdType id = ourClient.create().resource(bundle).execute().getId(); - - 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()); - } - - } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java index 34daa680a1d..26ced1d0dfa 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java @@ -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. - * + *

    * 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 diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4BundleTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4BundleTest.java index 075ed457670..971b0d56fce 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4BundleTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4BundleTest.java @@ -1,46 +1,69 @@ 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); + /** + * See #401 + */ + @Test + public void testBundlePreservesFullUrl() { + + Bundle bundle = new Bundle(); + bundle.setType(BundleType.DOCUMENT); + + Composition composition = new Composition(); + composition.setTitle("Visit Summary"); + bundle.addEntry().setFullUrl("http://foo").setResource(composition); + + IIdType id = myClient.create().resource(bundle).execute().getId(); + + Bundle retBundle = myClient.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(); } - /** - * See #401 - */ - @Test - public void testBundlePreservesFullUrl() throws Exception { - - Bundle bundle = new Bundle(); - bundle.setType(BundleType.DOCUMENT); - - Composition composition = new Composition(); - composition.setTitle("Visit Summary"); - bundle.addEntry().setFullUrl("http://foo").setResource(composition); - - IIdType id = ourClient.create().resource(bundle).execute().getId(); - - 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()); - } - - } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java index 29e8e0f549d..e2cbb97fd56 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java @@ -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); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationDstu3Test.java index 52a2607ee69..ce8c0a5712b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationDstu3Test.java @@ -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 Optional 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 toExpandedCodes(ValueSet theExpanded) { return theExpanded .getExpansion() diff --git a/hapi-fhir-jpaserver-uhnfhirtest/derby_maintenance.txt b/hapi-fhir-jpaserver-uhnfhirtest/derby_maintenance.txt index 698080e11d6..b2e6da3861a 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/derby_maintenance.txt +++ b/hapi-fhir-jpaserver-uhnfhirtest/derby_maintenance.txt @@ -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; diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java index 1dd093562fd..6c3e2be0607 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java @@ -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 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())); + } } /** diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java index d6b1caa9d79..f898adf2654 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java @@ -1246,7 +1246,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer(); + myReturnParams = new ArrayList<>(); if (theReturnParams != null) { for (OperationParam next : theReturnParams) { ReturnType type = new ReturnType(); diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/tenant/UrlBaseTenantIdentificationStrategy.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/tenant/UrlBaseTenantIdentificationStrategy.java index e444bc5d9ac..7887cbd9b8d 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/tenant/UrlBaseTenantIdentificationStrategy.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/tenant/UrlBaseTenantIdentificationStrategy.java @@ -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); } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientR4Test.java index 60c788985dd..8ab4dca6ac0 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/ClientR4Test.java @@ -958,6 +958,8 @@ public class ClientR4Test { } + + @Test public void testSearchWithStringIncludes() throws Exception { diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java index edfd12cb03e..1acbbc3cc5e 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java @@ -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,66 +45,67 @@ 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; public class GenericClientTest { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClientTest.class); - private static FhirContext ourCtx; - private HttpClient myHttpClient; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClientTest.class); + private static FhirContext ourCtx; + private HttpClient myHttpClient; - private HttpResponse myHttpResponse; + private HttpResponse myHttpResponse; - @Before - public void before() { + @Before + public void before() { - myHttpClient = mock(HttpClient.class, new ReturnsDeepStubs()); - ourCtx.getRestfulClientFactory().setHttpClient(myHttpClient); - ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + myHttpClient = mock(HttpClient.class, new ReturnsDeepStubs()); + ourCtx.getRestfulClientFactory().setHttpClient(myHttpClient); + ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); - myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); + myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); - System.setProperty(BaseClient.HAPI_CLIENT_KEEPRESPONSES, "true"); - } + System.setProperty(BaseClient.HAPI_CLIENT_KEEPRESPONSES, "true"); + } - private Patient createPatientP1() { - Patient p1 = new Patient(); - p1.addIdentifier().setSystem("foo:bar").setValue("12345"); - p1.addName().setFamily("Smith").addGiven("John"); - return p1; - } + private Patient createPatientP1() { + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("foo:bar").setValue("12345"); + p1.addName().setFamily("Smith").addGiven("John"); + return p1; + } - private Bundle createTransactionBundleInput() { - Bundle input = new Bundle(); - input.setType(BundleType.TRANSACTION); - input - .addEntry() - .setResource(createPatientP1()) - .getRequest() - .setMethod(HTTPVerb.POST); - return input; - } + private Bundle createTransactionBundleInput() { + Bundle input = new Bundle(); + input.setType(BundleType.TRANSACTION); + input + .addEntry() + .setResource(createPatientP1()) + .getRequest() + .setMethod(HTTPVerb.POST); + return input; + } - private Bundle createTransactionBundleOutput() { - Bundle output = new Bundle(); - output.setType(BundleType.TRANSACTIONRESPONSE); - output - .addEntry() - .setResource(createPatientP1()) - .getResponse() - .setLocation(createPatientP1().getId()); - return output; - } + private Bundle createTransactionBundleOutput() { + Bundle output = new Bundle(); + output.setType(BundleType.TRANSACTIONRESPONSE); + output + .addEntry() + .setResource(createPatientP1()) + .getResponse() + .setLocation(createPatientP1().getId()); + return output; + } - private String extractBody(ArgumentCaptor capt, int count) throws IOException { - String body = IOUtils.toString(((HttpEntityEnclosingRequestBase) capt.getAllValues().get(count)).getEntity().getContent(), "UTF-8"); - return body; - } + private String extractBody(ArgumentCaptor capt, int count) throws IOException { + String body = IOUtils.toString(((HttpEntityEnclosingRequestBase) capt.getAllValues().get(count)).getEntity().getContent(), "UTF-8"); + return body; + } - private String getPatientFeedWithOneResult() { - return ClientR4Test.getPatientFeedWithOneResult(ourCtx); + private String getPatientFeedWithOneResult() { + return ClientR4Test.getPatientFeedWithOneResult(ourCtx); // //@formatter:off // String msg = "\n" + // "\n" + @@ -127,13 +129,13 @@ public class GenericClientTest { // + " </entry>\n" // + "</feed>"; // //@formatter:on - // return msg; - } + // return msg; + } - private String getResourceResult() { - //@formatter:off + private String getResourceResult() { + //@formatter:off String msg = - "<Patient xmlns=\"http://hl7.org/fhir\">" + "<Patient xmlns=\"http://hl7.org/fhir\">" + "<text><status value=\"generated\" /><div xmlns=\"http://www.w3.org/1999/xhtml\">John Cardinal: 444333333 </div></text>" + "<identifier><label value=\"SSN\" /><system value=\"http://orionhealth.com/mrn\" /><value value=\"PRP1660\" /></identifier>" + "<name><use value=\"official\" /><family value=\"Cardinal\" /><given value=\"John\" /></name>" @@ -143,8 +145,8 @@ public class GenericClientTest { + "<address><use value=\"home\" /><line value=\"2222 Home Street\" /></address><active value=\"true\" />" + "</Patient>"; //@formatter:on - return msg; - } + return msg; + } @SuppressWarnings("unused") @Test @@ -221,1478 +223,1509 @@ public class GenericClientTest { assertEquals("no-cache, no-store", capt.getValue().getHeaders("Cache-Control")[0].getValue()); } - @Test - public void testCreatePopulatesIsCreated() throws Exception { + @Test + public void testCreatePopulatesIsCreated() throws Exception { - Patient p1 = createPatientP1(); - - 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.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"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); - MethodOutcome resp = client.create().resource(ourCtx.newXmlParser().encodeResourceToString(p1)).execute(); - assertTrue(resp.getCreated()); - - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - resp = client.create().resource(ourCtx.newXmlParser().encodeResourceToString(p1)).execute(); - assertNull(resp.getCreated()); - - ourLog.info("lastRequest: {}", ((GenericClient) client).getLastRequest()); - ourLog.info("lastResponse: {}", ((GenericClient) client).getLastResponse()); - ourLog.info("lastResponseBody: {}", ((GenericClient) client).getLastResponseBody()); - } - - @Test - public void testCreateWithStringAutoDetectsEncoding() throws Exception { - - Patient p1 = createPatientP1(); - - 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.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"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - int count = 0; - client.create().resource(ourCtx.newXmlParser().encodeResourceToString(p1)).execute(); - assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); - assertThat(extractBody(capt, count), containsString("value=\"John\"")); - count++; - - String resourceAsString = ourCtx.newJsonParser().encodeResourceToString(p1); - client - .create() - .resource(resourceAsString) - .execute(); - - assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.JSON.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); - assertThat(extractBody(capt, count), containsString("[\"John\"]")); - count++; - - /* - * e.g. Now try with reversed encoding (provide a string that's in JSON and ask the client to use XML) - */ - - client.create().resource(ourCtx.newXmlParser().encodeResourceToString(p1)).encodedJson().execute(); - assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.JSON.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); - assertThat(extractBody(capt, count), containsString("[\"John\"]")); - count++; - - client.create().resource(ourCtx.newJsonParser().encodeResourceToString(p1)).encodedXml().execute(); - assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); - assertThat(extractBody(capt, count), containsString("value=\"John\"")); - count++; - - } - - @Test - public void testCreateWithTag() throws Exception { - - Patient p1 = createPatientP1(); - p1.getMeta().addTag("http://hl7.org/fhir/tag", "urn:happytag", "This is a happy resource"); - - 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.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"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - MethodOutcome outcome = client.create().resource(p1).execute(); - assertEquals("44", outcome.getId().getIdPart()); - assertEquals("22", outcome.getId().getVersionIdPart()); - - int count = 0; - - assertEquals("http://example.com/fhir/Patient", capt.getValue().getURI().toString()); - assertEquals("POST", capt.getValue().getMethod()); - assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); - count++; - - /* - * Try fluent options - */ - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); - client.create().resource(p1).execute(); - assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(1).getURI().toString()); - assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); - count++; - - String resourceText = "<Patient xmlns=\"http://hl7.org/fhir\"> </Patient>"; - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); - client.create().resource(resourceText).execute(); - assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(2).getURI().toString()); - assertEquals(resourceText, IOUtils.toString(((HttpPost) capt.getAllValues().get(2)).getEntity().getContent())); - assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); - count++; - } - - @Test - public void testCreateWithTagNonFluent() throws Exception { - - Patient p1 = createPatientP1(); - p1.getMeta().addTag("http://hl7.org/fhir/tag", "urn:happytag", "This is a happy resource"); - - 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.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"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - MethodOutcome outcome = client.create().resource(p1).execute(); - assertEquals("44", outcome.getId().getIdPart()); - assertEquals("22", outcome.getId().getVersionIdPart()); - - assertEquals("http://example.com/fhir/Patient", capt.getValue().getURI().toString()); - assertEquals("POST", capt.getValue().getMethod()); - Header catH = capt.getValue().getFirstHeader("Category"); - assertNull(catH); - } - - /** - * Test for issue #60 - */ - @Test - public void testCreateWithUtf8Characters() throws Exception { - String name = "測試醫院"; - Organization org = new Organization(); - org.setName(name); - org.addIdentifier().setSystem("urn:system").setValue("testCreateWithUtf8Characters_01"); - - 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.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"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - int count = 0; - client.create().resource(org).prettyPrint().encodedXml().execute(); - assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); - assertThat(extractBody(capt, count), containsString("<name value=\"測試醫院\"/>")); - count++; - - } - - @Test - public void testDelete() throws Exception { - OperationOutcome oo = new OperationOutcome(); - oo.addIssue().addLocation("testDelete01"); - String ooStr = ourCtx.newXmlParser().encodeResourceToString(oo); - - 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.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"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - OperationOutcome outcome = (OperationOutcome) client.delete().resourceById("Patient", "123").execute(); - - assertEquals("http://example.com/fhir/Patient/123", capt.getValue().getURI().toString()); - assertEquals("DELETE", capt.getValue().getMethod()); - assertEquals("testDelete01", outcome.getIssueFirstRep().getLocation().get(0).getValue()); - - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("LKJHLKJGLKJKLL"), Charset.forName("UTF-8"))); - outcome = (OperationOutcome) client.delete().resourceById(new IdType("Location", "123", "456")).prettyPrint().encodedJson().execute(); - - assertEquals("http://example.com/fhir/Location/123?_pretty=true", capt.getAllValues().get(1).getURI().toString()); - assertEquals("DELETE", capt.getValue().getMethod()); - assertEquals(null, outcome); - - } - - @Test - public void testHistory() throws Exception { - - final String msg = getPatientFeedWithOneResult(); - - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() { - @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { - return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); - } - }); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - int idx = 0; - Bundle response; - - response = client - .history() - .onServer() - .andReturnBundle(Bundle.class) - .execute(); - assertEquals("http://example.com/fhir/_history", capt.getAllValues().get(idx).getURI().toString()); - assertEquals(1, response.getEntry().size()); - idx++; - - response = client - .history() - .onType(Patient.class) - .andReturnBundle(Bundle.class) - .execute(); - assertEquals("http://example.com/fhir/Patient/_history", capt.getAllValues().get(idx).getURI().toString()); - assertEquals(1, response.getEntry().size()); - idx++; - - response = client - .history() - .onInstance(new IdType("Patient", "123")) - .andReturnBundle(Bundle.class) - .execute(); - assertEquals("http://example.com/fhir/Patient/123/_history", capt.getAllValues().get(idx).getURI().toString()); - assertEquals(1, response.getEntry().size()); - idx++; - } - - @Test - @Ignore - public void testInvalidCalls() { - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - try { - client.meta(); - fail(); - } catch (IllegalStateException e) { - assertEquals("Can not call $meta operations on a DSTU1 client", e.getMessage()); - } - try { - client.operation(); - fail(); - } catch (IllegalStateException e) { - assertEquals("Operations are only supported in FHIR DSTU2 and later. This client was created using a context configured for DSTU1", e.getMessage()); - } - } - - @Test - public void testLoadPageAndReturnDstu1Bundle() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - - 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"))); - - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://foo"); - client - .loadPage() - .byUrl("http://example.com/page1") - .andReturnBundle(Bundle.class) - .execute(); - - assertEquals("http://example.com/page1", capt.getValue().getURI().toString()); - } - - @Test - public void testMissing() throws Exception { - - 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.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() { - @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { - return (new ReaderInputStream(new StringReader(getPatientFeedWithOneResult()), Charset.forName("UTF-8"))); - } - }); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); - - client.search().forResource("Patient").where(Patient.NAME.isMissing(true)).returnBundle(Bundle.class).execute(); - assertEquals("http://example.com/fhir/Patient?name%3Amissing=true", capt.getValue().getRequestLine().getUri()); - - client.search().forResource("Patient").where(Patient.NAME.isMissing(false)).returnBundle(Bundle.class).execute(); - assertEquals("http://example.com/fhir/Patient?name%3Amissing=false", capt.getValue().getRequestLine().getUri()); - } - - @Test - public void testRead() throws Exception { - - String msg = getResourceResult(); - - 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_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"), - new BasicHeader(Constants.HEADER_CONTENT_LOCATION, "http://foo.com/Patient/123/_history/2333"), - }; - when(myHttpResponse.getAllHeaders()).thenReturn(headers); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - Patient response = client - .read() - .resource(Patient.class) - .withId(new IdType("Patient/1234")) - .execute(); - - assertThat(response.getNameFirstRep().getFamily(), StringContains.containsString("Cardinal")); - - assertEquals("http://foo.com/Patient/123/_history/2333", response.getIdElement().getValue()); - - InstantType lm = response.getMeta().getLastUpdatedElement(); - lm.setTimeZoneZulu(true); - assertEquals("1995-11-15T04:58:08.000Z", lm.getValueAsString()); - - } - - @Test - public void testReadFluent() throws Exception { - - String msg = getResourceResult(); - - 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_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"), - new BasicHeader(Constants.HEADER_CONTENT_LOCATION, "http://foo.com/Patient/123/_history/2333"), - }; - when(myHttpResponse.getAllHeaders()).thenReturn(headers); + Patient p1 = createPatientP1(); + + 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.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"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); + MethodOutcome resp = client.create().resource(ourCtx.newXmlParser().encodeResourceToString(p1)).execute(); + assertTrue(resp.getCreated()); + + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + resp = client.create().resource(ourCtx.newXmlParser().encodeResourceToString(p1)).execute(); + assertNull(resp.getCreated()); + + ourLog.info("lastRequest: {}", ((GenericClient) client).getLastRequest()); + ourLog.info("lastResponse: {}", ((GenericClient) client).getLastResponse()); + ourLog.info("lastResponseBody: {}", ((GenericClient) client).getLastResponseBody()); + } + + @Test + public void testCreateWithStringAutoDetectsEncoding() throws Exception { + + Patient p1 = createPatientP1(); + + 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.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"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + int count = 0; + client.create().resource(ourCtx.newXmlParser().encodeResourceToString(p1)).execute(); + assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); + assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertThat(extractBody(capt, count), containsString("value=\"John\"")); + count++; + + String resourceAsString = ourCtx.newJsonParser().encodeResourceToString(p1); + client + .create() + .resource(resourceAsString) + .execute(); + + assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); + assertEquals(EncodingEnum.JSON.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertThat(extractBody(capt, count), containsString("[\"John\"]")); + count++; + + /* + * e.g. Now try with reversed encoding (provide a string that's in JSON and ask the client to use XML) + */ + + client.create().resource(ourCtx.newXmlParser().encodeResourceToString(p1)).encodedJson().execute(); + assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); + assertEquals(EncodingEnum.JSON.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertThat(extractBody(capt, count), containsString("[\"John\"]")); + count++; + + client.create().resource(ourCtx.newJsonParser().encodeResourceToString(p1)).encodedXml().execute(); + assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); + assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertThat(extractBody(capt, count), containsString("value=\"John\"")); + count++; + + } + + @Test + public void testCreateWithTag() throws Exception { + + Patient p1 = createPatientP1(); + p1.getMeta().addTag("http://hl7.org/fhir/tag", "urn:happytag", "This is a happy resource"); + + 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.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"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + MethodOutcome outcome = client.create().resource(p1).execute(); + assertEquals("44", outcome.getId().getIdPart()); + assertEquals("22", outcome.getId().getVersionIdPart()); + + int count = 0; + + assertEquals("http://example.com/fhir/Patient", capt.getValue().getURI().toString()); + assertEquals("POST", capt.getValue().getMethod()); + assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); + assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + count++; + + /* + * Try fluent options + */ + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); + client.create().resource(p1).execute(); + assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(1).getURI().toString()); + assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); + assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + count++; + + String resourceText = "<Patient xmlns=\"http://hl7.org/fhir\"> </Patient>"; + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); + client.create().resource(resourceText).execute(); + assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(2).getURI().toString()); + assertEquals(resourceText, IOUtils.toString(((HttpPost) capt.getAllValues().get(2)).getEntity().getContent())); + assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); + assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + count++; + } + + @Test + public void testCreateWithTagNonFluent() throws Exception { + + Patient p1 = createPatientP1(); + p1.getMeta().addTag("http://hl7.org/fhir/tag", "urn:happytag", "This is a happy resource"); + + 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.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"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + MethodOutcome outcome = client.create().resource(p1).execute(); + assertEquals("44", outcome.getId().getIdPart()); + assertEquals("22", outcome.getId().getVersionIdPart()); + + assertEquals("http://example.com/fhir/Patient", capt.getValue().getURI().toString()); + assertEquals("POST", capt.getValue().getMethod()); + Header catH = capt.getValue().getFirstHeader("Category"); + assertNull(catH); + } + + /** + * Test for issue #60 + */ + @Test + public void testCreateWithUtf8Characters() throws Exception { + String name = "測試醫院"; + Organization org = new Organization(); + org.setName(name); + org.addIdentifier().setSystem("urn:system").setValue("testCreateWithUtf8Characters_01"); + + 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.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"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + int count = 0; + client.create().resource(org).prettyPrint().encodedXml().execute(); + assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); + assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertThat(extractBody(capt, count), containsString("<name value=\"測試醫院\"/>")); + count++; + + } + + @Test + public void testDelete() throws Exception { + OperationOutcome oo = new OperationOutcome(); + oo.addIssue().addLocation("testDelete01"); + String ooStr = ourCtx.newXmlParser().encodeResourceToString(oo); + + 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.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"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + OperationOutcome outcome = (OperationOutcome) client.delete().resourceById("Patient", "123").execute(); + + assertEquals("http://example.com/fhir/Patient/123", capt.getValue().getURI().toString()); + assertEquals("DELETE", capt.getValue().getMethod()); + assertEquals("testDelete01", outcome.getIssueFirstRep().getLocation().get(0).getValue()); + + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("LKJHLKJGLKJKLL"), Charset.forName("UTF-8"))); + outcome = (OperationOutcome) client.delete().resourceById(new IdType("Location", "123", "456")).prettyPrint().encodedJson().execute(); + + assertEquals("http://example.com/fhir/Location/123?_pretty=true", capt.getAllValues().get(1).getURI().toString()); + assertEquals("DELETE", capt.getValue().getMethod()); + assertEquals(null, outcome); + + } + + @Test + public void testHistory() throws Exception { + + final String msg = getPatientFeedWithOneResult(); + + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() { + @Override + public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + int idx = 0; + Bundle response; + + response = client + .history() + .onServer() + .andReturnBundle(Bundle.class) + .execute(); + assertEquals("http://example.com/fhir/_history", capt.getAllValues().get(idx).getURI().toString()); + assertEquals(1, response.getEntry().size()); + idx++; + + response = client + .history() + .onType(Patient.class) + .andReturnBundle(Bundle.class) + .execute(); + assertEquals("http://example.com/fhir/Patient/_history", capt.getAllValues().get(idx).getURI().toString()); + assertEquals(1, response.getEntry().size()); + idx++; + + response = client + .history() + .onInstance(new IdType("Patient", "123")) + .andReturnBundle(Bundle.class) + .execute(); + assertEquals("http://example.com/fhir/Patient/123/_history", capt.getAllValues().get(idx).getURI().toString()); + assertEquals(1, response.getEntry().size()); + idx++; + } + + @Test + @Ignore + public void testInvalidCalls() { + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + try { + client.meta(); + fail(); + } catch (IllegalStateException e) { + assertEquals("Can not call $meta operations on a DSTU1 client", e.getMessage()); + } + try { + client.operation(); + fail(); + } catch (IllegalStateException e) { + assertEquals("Operations are only supported in FHIR DSTU2 and later. This client was created using a context configured for DSTU1", e.getMessage()); + } + } + + @Test + public void testLoadPageAndReturnDstu1Bundle() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + + 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"))); + + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://foo"); + client + .loadPage() + .byUrl("http://example.com/page1") + .andReturnBundle(Bundle.class) + .execute(); + + assertEquals("http://example.com/page1", capt.getValue().getURI().toString()); + } + + @Test + public void testMissing() throws Exception { + + 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.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() { + @Override + public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + return (new ReaderInputStream(new StringReader(getPatientFeedWithOneResult()), Charset.forName("UTF-8"))); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); + + client.search().forResource("Patient").where(Patient.NAME.isMissing(true)).returnBundle(Bundle.class).execute(); + assertEquals("http://example.com/fhir/Patient?name%3Amissing=true", capt.getValue().getRequestLine().getUri()); + + client.search().forResource("Patient").where(Patient.NAME.isMissing(false)).returnBundle(Bundle.class).execute(); + 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 { + + String msg = getResourceResult(); + + 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_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"), + new BasicHeader(Constants.HEADER_CONTENT_LOCATION, "http://foo.com/Patient/123/_history/2333"), + }; + when(myHttpResponse.getAllHeaders()).thenReturn(headers); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Patient response = client + .read() + .resource(Patient.class) + .withId(new IdType("Patient/1234")) + .execute(); + + assertThat(response.getNameFirstRep().getFamily(), StringContains.containsString("Cardinal")); + + assertEquals("http://foo.com/Patient/123/_history/2333", response.getIdElement().getValue()); + + InstantType lm = response.getMeta().getLastUpdatedElement(); + lm.setTimeZoneZulu(true); + assertEquals("1995-11-15T04:58:08.000Z", lm.getValueAsString()); + + } + + @Test + public void testReadFluent() throws Exception { + + String msg = getResourceResult(); + + 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_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"), + new BasicHeader(Constants.HEADER_CONTENT_LOCATION, "http://foo.com/Patient/123/_history/2333"), + }; + when(myHttpResponse.getAllHeaders()).thenReturn(headers); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + int count = 0; + + Patient response = client.read().resource(Patient.class).withId(new IdType("Patient/1234")).execute(); + assertThat(response.getNameFirstRep().getFamily(), StringContains.containsString("Cardinal")); + assertEquals("http://example.com/fhir/Patient/1234", capt.getAllValues().get(count++).getURI().toString()); - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + response = (Patient) client.read().resource("Patient").withId("1234").execute(); + assertThat(response.getNameFirstRep().getFamily(), StringContains.containsString("Cardinal")); + assertEquals("http://example.com/fhir/Patient/1234", capt.getAllValues().get(count++).getURI().toString()); + + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + response = (Patient) client.read().resource("Patient").withId(567L).execute(); + assertThat(response.getNameFirstRep().getFamily(), StringContains.containsString("Cardinal")); + assertEquals("http://example.com/fhir/Patient/567", capt.getAllValues().get(count++).getURI().toString()); + + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + response = client.read().resource(Patient.class).withIdAndVersion("1234", "22").execute(); + assertThat(response.getNameFirstRep().getFamily(), StringContains.containsString("Cardinal")); + assertEquals("http://example.com/fhir/Patient/1234/_history/22", capt.getAllValues().get(count++).getURI().toString()); + + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + response = client.read().resource(Patient.class).withUrl("http://foo/Patient/22").execute(); + assertThat(response.getNameFirstRep().getFamily(), StringContains.containsString("Cardinal")); + assertEquals("http://foo/Patient/22", capt.getAllValues().get(count++).getURI().toString()); - int count = 0; + } - Patient response = client.read().resource(Patient.class).withId(new IdType("Patient/1234")).execute(); - assertThat(response.getNameFirstRep().getFamily(), StringContains.containsString("Cardinal")); - assertEquals("http://example.com/fhir/Patient/1234", capt.getAllValues().get(count++).getURI().toString()); + @Test + public void testReadWithAbsoluteUrl() throws Exception { - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - response = (Patient) client.read().resource("Patient").withId("1234").execute(); - assertThat(response.getNameFirstRep().getFamily(), StringContains.containsString("Cardinal")); - assertEquals("http://example.com/fhir/Patient/1234", capt.getAllValues().get(count++).getURI().toString()); + String msg = getResourceResult(); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - response = (Patient) client.read().resource("Patient").withId(567L).execute(); - assertThat(response.getNameFirstRep().getFamily(), StringContains.containsString("Cardinal")); - assertEquals("http://example.com/fhir/Patient/567", capt.getAllValues().get(count++).getURI().toString()); - - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - response = client.read().resource(Patient.class).withIdAndVersion("1234", "22").execute(); - assertThat(response.getNameFirstRep().getFamily(), StringContains.containsString("Cardinal")); - assertEquals("http://example.com/fhir/Patient/1234/_history/22", capt.getAllValues().get(count++).getURI().toString()); - - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - response = client.read().resource(Patient.class).withUrl("http://foo/Patient/22").execute(); - assertThat(response.getNameFirstRep().getFamily(), StringContains.containsString("Cardinal")); - assertEquals("http://foo/Patient/22", capt.getAllValues().get(count++).getURI().toString()); + 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_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"), + new BasicHeader(Constants.HEADER_CONTENT_LOCATION, "http://foo.com/Patient/123/_history/2333"), + }; + when(myHttpResponse.getAllHeaders()).thenReturn(headers); - } + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - @Test - public void testReadWithAbsoluteUrl() throws Exception { + Patient response = client + .read() + .resource(Patient.class) + .withUrl(new IdType("http://somebase.com/path/to/base/Patient/1234")) + .execute(); + assertThat(response.getNameFirstRep().getFamily(), StringContains.containsString("Cardinal")); + assertEquals("http://somebase.com/path/to/base/Patient/1234", capt.getAllValues().get(0).getURI().toString()); - String msg = getResourceResult(); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + response = client + .read() + .resource(Patient.class) + .withUrl(new IdType("http://somebase.com/path/to/base/Patient/1234/_history/222")) + .execute(); + assertThat(response.getNameFirstRep().getFamily(), StringContains.containsString("Cardinal")); + assertEquals("http://somebase.com/path/to/base/Patient/1234/_history/222", capt.getAllValues().get(1).getURI().toString()); - 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_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"), - new BasicHeader(Constants.HEADER_CONTENT_LOCATION, "http://foo.com/Patient/123/_history/2333"), - }; - when(myHttpResponse.getAllHeaders()).thenReturn(headers); + } - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + @SuppressWarnings("unused") + @Test + public void testSearchAllResources() throws Exception { - Patient response = client - .read() - .resource(Patient.class) - .withUrl(new IdType("http://somebase.com/path/to/base/Patient/1234")) - .execute(); - assertThat(response.getNameFirstRep().getFamily(), StringContains.containsString("Cardinal")); - assertEquals("http://somebase.com/path/to/base/Patient/1234", capt.getAllValues().get(0).getURI().toString()); + String msg = getPatientFeedWithOneResult(); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - response = client - .read() - .resource(Patient.class) - .withUrl(new IdType("http://somebase.com/path/to/base/Patient/1234/_history/222")) - .execute(); - assertThat(response.getNameFirstRep().getFamily(), StringContains.containsString("Cardinal")); - assertEquals("http://somebase.com/path/to/base/Patient/1234/_history/222", capt.getAllValues().get(1).getURI().toString()); + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - } + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - @SuppressWarnings("unused") - @Test - public void testSearchAllResources() throws Exception { + Bundle response = client.search() + .forAllResources() + .where(Patient.NAME.matches().value("james")) + .returnBundle(Bundle.class) + .execute(); + + assertEquals("http://example.com/fhir/?name=james", capt.getValue().getURI().toString()); + + } + + @SuppressWarnings("unused") + @Test + public void testSearchAutomaticallyUsesPost() throws Exception { - String msg = getPatientFeedWithOneResult(); + String msg = getPatientFeedWithOneResult(); - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + String longValue = StringUtils.leftPad("", 20000, 'B'); + + Bundle response = client.search() + .forResource("Patient") + .where(Patient.NAME.matches().value(longValue)) + .returnBundle(Bundle.class) + .execute(); + + assertEquals("http://example.com/fhir/Patient/_search", capt.getValue().getURI().toString()); + + HttpEntityEnclosingRequestBase enc = (HttpEntityEnclosingRequestBase) capt.getValue(); + UrlEncodedFormEntity ent = (UrlEncodedFormEntity) enc.getEntity(); + String string = IOUtils.toString(ent.getContent()); + ourLog.info(string); + assertEquals("name=" + longValue, string); + } + + @Test + public void testSearchByCompartment() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + + 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"))); + + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://foo"); + Bundle response = client + .search() + .forResource(Patient.class) + .withIdAndCompartment("123", "fooCompartment") + .where(Patient.BIRTHDATE.afterOrEquals().day("2011-01-02")) + .returnBundle(Bundle.class) + .execute(); + + assertEquals("http://foo/Patient/123/fooCompartment?birthdate=ge2011-01-02", capt.getValue().getURI().toString()); + + ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(response)); + + assertEquals("PRP1660", BundleUtil.toListOfResourcesOfType(ourCtx, response, Patient.class).get(0).getIdentifier().get(0).getValue()); + + try { + client + .search() + .forResource(Patient.class) + .withIdAndCompartment("", "fooCompartment") + .where(Patient.BIRTHDATE.afterOrEquals().day("2011-01-02")) + .returnBundle(Bundle.class) + .execute(); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.toString(), containsString("null or empty for compartment")); + } + + } + + @SuppressWarnings("unused") + @Test + public void testSearchByComposite() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://foo"); + + Bundle response = client.search() + .forResource("Observation") + .where(Observation.CODE_VALUE_DATE + .withLeft(Observation.CODE.exactly().code("FOO$BAR")) + .withRight(Observation.VALUE_DATE.exactly().day("2001-01-01"))) + .returnBundle(Bundle.class) + .execute(); + + assertEquals("http://foo/Observation?" + Observation.SP_CODE_VALUE_DATE + "=" + UrlUtil.escapeUrlParam("FOO\\$BAR$2001-01-01"), capt.getValue().getURI().toString()); + + } + + @SuppressWarnings("unused") + @Test + public void testSearchByDate() throws Exception { + + final String msg = getPatientFeedWithOneResult(); + + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() { + @Override + public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + int idx = 0; + + @SuppressWarnings("deprecation") + Bundle response = client.search() + .forResource(Patient.class) + .encodedJson() + .where(Patient.BIRTHDATE.beforeOrEquals().day("2012-01-22")) + .and(Patient.BIRTHDATE.after().day("2011-01-01")) + .include(Patient.INCLUDE_ORGANIZATION) + .sort().ascending(Patient.BIRTHDATE) + .sort().descending(Patient.NAME) + .sort().defaultOrder(Patient.ADDRESS) + .count(123) + .returnBundle(Bundle.class) + .execute(); + + assertEquals("http://example.com/fhir/Patient?birthdate=le2012-01-22&birthdate=gt2011-01-01&_include=Patient%3Aorganization&_sort=birthdate%2C-name%2Caddress&_count=123&_format=json", + capt.getAllValues().get(idx++).getURI().toString()); + + response = client.search() + .forResource(Patient.class) + .encodedJson() + .where(Patient.BIRTHDATE.beforeOrEquals().day("2012-01-22")) + .and(Patient.BIRTHDATE.after().day("2011-01-01")) + .include(Patient.INCLUDE_ORGANIZATION) + .sort().ascending(Patient.BIRTHDATE) + .sort().descending(Patient.NAME) + .sort().defaultOrder(Patient.ADDRESS) + .count(123) + .returnBundle(Bundle.class) + .execute(); + + assertEquals("http://example.com/fhir/Patient?birthdate=le2012-01-22&birthdate=gt2011-01-01&_include=Patient%3Aorganization&_sort=birthdate%2C-name%2Caddress&_count=123&_format=json", + capt.getAllValues().get(idx++).getURI().toString()); + + response = client.search() + .forResource(Patient.class) + .encodedJson() + .where(Patient.BIRTHDATE.beforeOrEquals().day("2012-01-22").orAfter().day("2020-01-01")) + .and(Patient.BIRTHDATE.after().day("2011-01-01")) + .returnBundle(Bundle.class) + .execute(); + + String comma = "%2C"; + assertEquals("http://example.com/fhir/Patient?birthdate=le2012-01-22" + comma + "gt2020-01-01&birthdate=gt2011-01-01&_format=json", capt.getAllValues().get(idx++).getURI().toString()); + } + + @SuppressWarnings("unused") + @Test + public void testSearchByNumberExact() throws Exception { + + String msg = ourCtx.newXmlParser().encodeResourceToString(new Bundle()); + + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Bundle response = client.search() + .forResource(Observation.class) + .where(Observation.VALUE_QUANTITY.greaterThan().number(123).andUnits("foo", "bar")) + .returnBundle(Bundle.class) + .execute(); + + assertEquals("http://example.com/fhir/Observation?value-quantity=gt123%7Cfoo%7Cbar", capt.getValue().getURI().toString()); + } + + @SuppressWarnings("unused") + @Test + public void testSearchByProfile() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Bundle response = client.search() + .forResource(Patient.class) + .withProfile("http://1") + .withProfile("http://2") + .returnBundle(Bundle.class) + .execute(); + + assertEquals("http://example.com/fhir/Patient?_profile=http%3A%2F%2F1&_profile=http%3A%2F%2F2", capt.getValue().getURI().toString()); + + } + + @SuppressWarnings("unused") + @Test + public void testSearchByQuantity() throws Exception { - Bundle response = client.search() - .forAllResources() - .where(Patient.NAME.matches().value("james")) - .returnBundle(Bundle.class) - .execute(); - - assertEquals("http://example.com/fhir/?name=james", capt.getValue().getURI().toString()); - - } - - @SuppressWarnings("unused") - @Test - public void testSearchAutomaticallyUsesPost() throws Exception { + String msg = getPatientFeedWithOneResult(); - String msg = getPatientFeedWithOneResult(); + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - String longValue = StringUtils.leftPad("", 20000, 'B'); - - Bundle response = client.search() - .forResource("Patient") - .where(Patient.NAME.matches().value(longValue)) - .returnBundle(Bundle.class) - .execute(); - - assertEquals("http://example.com/fhir/Patient/_search", capt.getValue().getURI().toString()); - - HttpEntityEnclosingRequestBase enc = (HttpEntityEnclosingRequestBase) capt.getValue(); - UrlEncodedFormEntity ent = (UrlEncodedFormEntity) enc.getEntity(); - String string = IOUtils.toString(ent.getContent()); - ourLog.info(string); - assertEquals("name=" + longValue, string); - } - - @Test - public void testSearchByCompartment() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - - 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"))); - - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://foo"); - Bundle response = client - .search() - .forResource(Patient.class) - .withIdAndCompartment("123", "fooCompartment") - .where(Patient.BIRTHDATE.afterOrEquals().day("2011-01-02")) - .returnBundle(Bundle.class) - .execute(); - - assertEquals("http://foo/Patient/123/fooCompartment?birthdate=ge2011-01-02", capt.getValue().getURI().toString()); - - ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(response)); - - assertEquals("PRP1660", BundleUtil.toListOfResourcesOfType(ourCtx, response, Patient.class).get(0).getIdentifier().get(0).getValue()); - - try { - client - .search() - .forResource(Patient.class) - .withIdAndCompartment("", "fooCompartment") - .where(Patient.BIRTHDATE.afterOrEquals().day("2011-01-02")) - .returnBundle(Bundle.class) - .execute(); - fail(); - } catch (InvalidRequestException e) { - assertThat(e.toString(), containsString("null or empty for compartment")); - } - - } - - @SuppressWarnings("unused") - @Test - public void testSearchByComposite() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://foo"); - - Bundle response = client.search() - .forResource("Observation") - .where(Observation.CODE_VALUE_DATE - .withLeft(Observation.CODE.exactly().code("FOO$BAR")) - .withRight(Observation.VALUE_DATE.exactly().day("2001-01-01"))) - .returnBundle(Bundle.class) - .execute(); - - assertEquals("http://foo/Observation?" + Observation.SP_CODE_VALUE_DATE + "=" + UrlUtil.escapeUrlParam("FOO\\$BAR$2001-01-01"), capt.getValue().getURI().toString()); - - } - - @SuppressWarnings("unused") - @Test - public void testSearchByDate() throws Exception { - - final String msg = getPatientFeedWithOneResult(); - - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() { - @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { - return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); - } - }); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - int idx = 0; - - @SuppressWarnings("deprecation") - Bundle response = client.search() - .forResource(Patient.class) - .encodedJson() - .where(Patient.BIRTHDATE.beforeOrEquals().day("2012-01-22")) - .and(Patient.BIRTHDATE.after().day("2011-01-01")) - .include(Patient.INCLUDE_ORGANIZATION) - .sort().ascending(Patient.BIRTHDATE) - .sort().descending(Patient.NAME) - .sort().defaultOrder(Patient.ADDRESS) - .count(123) - .returnBundle(Bundle.class) - .execute(); - - assertEquals("http://example.com/fhir/Patient?birthdate=le2012-01-22&birthdate=gt2011-01-01&_include=Patient%3Aorganization&_sort=birthdate%2C-name%2Caddress&_count=123&_format=json", - capt.getAllValues().get(idx++).getURI().toString()); - - response = client.search() - .forResource(Patient.class) - .encodedJson() - .where(Patient.BIRTHDATE.beforeOrEquals().day("2012-01-22")) - .and(Patient.BIRTHDATE.after().day("2011-01-01")) - .include(Patient.INCLUDE_ORGANIZATION) - .sort().ascending(Patient.BIRTHDATE) - .sort().descending(Patient.NAME) - .sort().defaultOrder(Patient.ADDRESS) - .count(123) - .returnBundle(Bundle.class) - .execute(); - - assertEquals("http://example.com/fhir/Patient?birthdate=le2012-01-22&birthdate=gt2011-01-01&_include=Patient%3Aorganization&_sort=birthdate%2C-name%2Caddress&_count=123&_format=json", - capt.getAllValues().get(idx++).getURI().toString()); - - response = client.search() - .forResource(Patient.class) - .encodedJson() - .where(Patient.BIRTHDATE.beforeOrEquals().day("2012-01-22").orAfter().day("2020-01-01")) - .and(Patient.BIRTHDATE.after().day("2011-01-01")) - .returnBundle(Bundle.class) - .execute(); - - String comma = "%2C"; - assertEquals("http://example.com/fhir/Patient?birthdate=le2012-01-22" + comma + "gt2020-01-01&birthdate=gt2011-01-01&_format=json", capt.getAllValues().get(idx++).getURI().toString()); - } - - @SuppressWarnings("unused") - @Test - public void testSearchByNumberExact() throws Exception { - - String msg = ourCtx.newXmlParser().encodeResourceToString(new Bundle()); - - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - Bundle response = client.search() - .forResource(Observation.class) - .where(Observation.VALUE_QUANTITY.greaterThan().number(123).andUnits("foo", "bar")) - .returnBundle(Bundle.class) - .execute(); - - assertEquals("http://example.com/fhir/Observation?value-quantity=gt123%7Cfoo%7Cbar", capt.getValue().getURI().toString()); - } - - @SuppressWarnings("unused") - @Test - public void testSearchByProfile() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - Bundle response = client.search() - .forResource(Patient.class) - .withProfile("http://1") - .withProfile("http://2") - .returnBundle(Bundle.class) - .execute(); - - assertEquals("http://example.com/fhir/Patient?_profile=http%3A%2F%2F1&_profile=http%3A%2F%2F2", capt.getValue().getURI().toString()); - - } - - @SuppressWarnings("unused") - @Test - public void testSearchByQuantity() throws Exception { + Bundle response = client.search() + .forResource(Patient.class) + .where(Encounter.LENGTH.exactly().number(123).andNoUnits()) + .returnBundle(Bundle.class) + .execute(); - String msg = getPatientFeedWithOneResult(); + assertEquals("http://example.com/fhir/Patient?length=123%7C%7C", capt.getValue().getURI().toString()); - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + } - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + @SuppressWarnings("unused") + @Test + public void testSearchByReferenceProperty() throws Exception { - Bundle response = client.search() - .forResource(Patient.class) - .where(Encounter.LENGTH.exactly().number(123).andNoUnits()) - .returnBundle(Bundle.class) - .execute(); + String msg = getPatientFeedWithOneResult(); - assertEquals("http://example.com/fhir/Patient?length=123%7C%7C", capt.getValue().getURI().toString()); + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - } + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - @SuppressWarnings("unused") - @Test - public void testSearchByReferenceProperty() throws Exception { + Bundle response = client.search() + .forResource(Patient.class) + .where(Patient.GENERAL_PRACTITIONER.hasChainedProperty(Organization.NAME.matches().value("ORG0"))) + .returnBundle(Bundle.class) + .execute(); - String msg = getPatientFeedWithOneResult(); + assertEquals("http://example.com/fhir/Patient?general-practitioner.name=ORG0", capt.getValue().getURI().toString()); - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + } - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + @SuppressWarnings("unused") + @Test + public void testSearchByReferenceSimple() throws Exception { - Bundle response = client.search() - .forResource(Patient.class) - .where(Patient.GENERAL_PRACTITIONER.hasChainedProperty(Organization.NAME.matches().value("ORG0"))) - .returnBundle(Bundle.class) - .execute(); + String msg = getPatientFeedWithOneResult(); - assertEquals("http://example.com/fhir/Patient?general-practitioner.name=ORG0", capt.getValue().getURI().toString()); + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - } + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - @SuppressWarnings("unused") - @Test - public void testSearchByReferenceSimple() throws Exception { + Bundle response = client.search() + .forResource("Patient") + .where(Patient.GENERAL_PRACTITIONER.hasId("123")) + .returnBundle(Bundle.class) + .execute(); - String msg = getPatientFeedWithOneResult(); + assertEquals("http://example.com/fhir/Patient?general-practitioner=123", capt.getValue().getURI().toString()); - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + } - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + @SuppressWarnings("unused") + @Test + public void testSearchBySecurity() throws Exception { - Bundle response = client.search() - .forResource("Patient") - .where(Patient.GENERAL_PRACTITIONER.hasId("123")) - .returnBundle(Bundle.class) - .execute(); + String msg = getPatientFeedWithOneResult(); - assertEquals("http://example.com/fhir/Patient?general-practitioner=123", capt.getValue().getURI().toString()); + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - } + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - @SuppressWarnings("unused") - @Test - public void testSearchBySecurity() throws Exception { + Bundle response = client.search() + .forResource(Patient.class) + .withSecurity("urn:foo", "123") + .withSecurity("urn:bar", "456") + .returnBundle(Bundle.class) + .execute(); - String msg = getPatientFeedWithOneResult(); + assertEquals("http://example.com/fhir/Patient?_security=urn%3Afoo%7C123&_security=urn%3Abar%7C456", capt.getValue().getURI().toString()); - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + } - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + @SuppressWarnings("unused") + @Test + public void testSearchByString() throws Exception { - Bundle response = client.search() - .forResource(Patient.class) - .withSecurity("urn:foo", "123") - .withSecurity("urn:bar", "456") - .returnBundle(Bundle.class) - .execute(); + String msg = getPatientFeedWithOneResult(); - assertEquals("http://example.com/fhir/Patient?_security=urn%3Afoo%7C123&_security=urn%3Abar%7C456", capt.getValue().getURI().toString()); + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - } + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - @SuppressWarnings("unused") - @Test - public void testSearchByString() throws Exception { + Bundle response = client.search() + .forResource("Patient") + .where(Patient.NAME.matches().value("james")) + .returnBundle(Bundle.class) + .execute(); - String msg = getPatientFeedWithOneResult(); + assertEquals("http://example.com/fhir/Patient?name=james", capt.getValue().getURI().toString()); - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + response = client.search() + .forResource("Patient") + .where(Patient.NAME.matches().values("AAA", "BBB", "C,C")) + .returnBundle(Bundle.class) + .execute(); - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + assertEquals("http://example.com/fhir/Patient?name=" + UrlUtil.escapeUrlParam("AAA,BBB,C\\,C"), capt.getAllValues().get(1).getURI().toString()); - Bundle response = client.search() - .forResource("Patient") - .where(Patient.NAME.matches().value("james")) - .returnBundle(Bundle.class) - .execute(); + } - assertEquals("http://example.com/fhir/Patient?name=james", capt.getValue().getURI().toString()); + @SuppressWarnings("unused") + @Test + public void testSearchByStringExact() throws Exception { - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - response = client.search() - .forResource("Patient") - .where(Patient.NAME.matches().values("AAA", "BBB", "C,C")) - .returnBundle(Bundle.class) - .execute(); + String msg = getPatientFeedWithOneResult(); - assertEquals("http://example.com/fhir/Patient?name=" + UrlUtil.escapeUrlParam("AAA,BBB,C\\,C"), capt.getAllValues().get(1).getURI().toString()); + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - } + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - @SuppressWarnings("unused") - @Test - public void testSearchByStringExact() throws Exception { + Bundle response = client.search() + .forResource("Patient") + .where(Patient.NAME.matchesExactly().value("james")) + .returnBundle(Bundle.class) + .execute(); - String msg = getPatientFeedWithOneResult(); + assertEquals("http://example.com/fhir/Patient?name%3Aexact=james", capt.getValue().getURI().toString()); - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + } - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + @SuppressWarnings("unused") + @Test + public void testSearchByTag() throws Exception { - Bundle response = client.search() - .forResource("Patient") - .where(Patient.NAME.matchesExactly().value("james")) - .returnBundle(Bundle.class) - .execute(); + String msg = getPatientFeedWithOneResult(); - assertEquals("http://example.com/fhir/Patient?name%3Aexact=james", capt.getValue().getURI().toString()); + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Bundle response = client.search() + .forResource(Patient.class) + .withTag("urn:foo", "123") + .withTag("urn:bar", "456") + .returnBundle(Bundle.class) + .execute(); + + assertEquals("http://example.com/fhir/Patient?_tag=urn%3Afoo%7C123&_tag=urn%3Abar%7C456", capt.getValue().getURI().toString()); + + } + + @SuppressWarnings("unused") + @Test + public void testSearchByToken() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Bundle response = client.search() + .forResource("Patient") + .where(Patient.IDENTIFIER.exactly().systemAndCode("http://example.com/fhir", "ZZZ")) + .returnBundle(Bundle.class) + .execute(); + + assertEquals("http://example.com/fhir/Patient?identifier=http%3A%2F%2Fexample.com%2Ffhir%7CZZZ", capt.getValue().getURI().toString()); + + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + response = client.search() + .forResource("Patient") + .where(Patient.IDENTIFIER.exactly().code("ZZZ")) + .returnBundle(Bundle.class) + .execute(); + + assertEquals("http://example.com/fhir/Patient?identifier=ZZZ", capt.getAllValues().get(1).getURI().toString()); + + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + response = client.search() + .forResource("Patient") + .where(Patient.IDENTIFIER.exactly().codings(new Coding("A", "B", "ZZZ"), new Coding("C", "D", "ZZZ"))) + .returnBundle(Bundle.class) + .execute(); + + assertEquals("http://example.com/fhir/Patient?identifier=" + UrlUtil.escapeUrlParam("A|B,C|D"), capt.getAllValues().get(2).getURI().toString()); + + } + + /** + * Test for #192 + */ + @SuppressWarnings("unused") + @Test + public void testSearchByTokenWithEscaping() throws Exception { + final String msg = getPatientFeedWithOneResult(); + + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() { + @Override + public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://foo"); + int index = 0; + String wantPrefix = "http://foo/Patient?identifier="; + + Bundle response = client.search() + .forResource("Patient") + .where(Patient.IDENTIFIER.exactly().systemAndCode("1", "2")) + .returnBundle(Bundle.class) + .execute(); + String wantValue = "1|2"; + String url = capt.getAllValues().get(index).getURI().toString(); + assertThat(url, Matchers.startsWith(wantPrefix)); + assertEquals(wantValue, UrlUtil.unescape(url.substring(wantPrefix.length()))); + assertEquals(UrlUtil.escapeUrlParam(wantValue), url.substring(wantPrefix.length())); + index++; + + response = client.search() + .forResource("Patient") + .where(Patient.IDENTIFIER.exactly().systemAndCode("1,2", "3,4")) + .returnBundle(Bundle.class) + .execute(); + wantValue = "1\\,2|3\\,4"; + url = capt.getAllValues().get(index).getURI().toString(); + assertThat(url, Matchers.startsWith(wantPrefix)); + assertEquals(wantValue, UrlUtil.unescape(url.substring(wantPrefix.length()))); + assertEquals(UrlUtil.escapeUrlParam(wantValue), url.substring(wantPrefix.length())); + index++; + } + + @SuppressWarnings("unused") + @Test + public void testSearchByTokenWithSystemAndNoCode() throws Exception { + + final String msg = getPatientFeedWithOneResult(); + + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() { + @Override + public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + int idx = 0; + + Bundle response = client.search() + .forResource("Patient") + .where(Patient.IDENTIFIER.hasSystemWithAnyCode("urn:foo")) + .returnBundle(Bundle.class) + .execute(); + assertEquals("http://example.com/fhir/Patient?identifier=urn%3Afoo%7C", capt.getAllValues().get(idx++).getURI().toString()); + + response = client.search() + .forResource("Patient") + .where(Patient.IDENTIFIER.exactly().systemAndCode("urn:foo", null)) + .returnBundle(Bundle.class) + .execute(); + assertEquals("http://example.com/fhir/Patient?identifier=urn%3Afoo%7C", capt.getAllValues().get(idx++).getURI().toString()); + + response = client.search() + .forResource("Patient") + .where(Patient.IDENTIFIER.exactly().systemAndCode("urn:foo", "")) + .returnBundle(Bundle.class) + .execute(); + assertEquals("http://example.com/fhir/Patient?identifier=urn%3Afoo%7C", capt.getAllValues().get(idx++).getURI().toString()); + } + + @SuppressWarnings("unused") + @Test + public void testSearchIncludeRecursive() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Bundle response = client.search() + .forResource(Patient.class) + .include(Patient.INCLUDE_ORGANIZATION) + .include(Patient.INCLUDE_LINK.asRecursive()) + .include(Patient.INCLUDE_ALL.asNonRecursive()) + .returnBundle(Bundle.class) + .execute(); + + assertThat(capt.getValue().getURI().toString(), containsString("http://example.com/fhir/Patient?")); + assertThat(capt.getValue().getURI().toString(), containsString("_include=" + UrlUtil.escapeUrlParam(Patient.INCLUDE_ORGANIZATION.getValue()))); + assertThat(capt.getValue().getURI().toString(), containsString("_include%3Arecurse=" + UrlUtil.escapeUrlParam(Patient.INCLUDE_LINK.getValue()))); + assertThat(capt.getValue().getURI().toString(), containsString("_include=*")); + + } + + @SuppressWarnings("unused") + @Test + public void testSearchUsingGetSearch() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Bundle response = client.search() + .forResource("Patient") + .where(Patient.NAME.matches().value("james")) + .usingStyle(SearchStyleEnum.GET_WITH_SEARCH) + .returnBundle(Bundle.class) + .execute(); + + assertEquals("http://example.com/fhir/Patient/_search?name=james", capt.getValue().getURI().toString()); + } + + @SuppressWarnings("unused") + @Test + public void testSearchUsingPost() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Bundle response = client.search() + .forResource("Patient") + .where(Patient.NAME.matches().value("james")) + .usingStyle(SearchStyleEnum.POST) + .returnBundle(Bundle.class) + .execute(); + + assertEquals("http://example.com/fhir/Patient/_search", capt.getValue().getURI().toString()); + + HttpEntityEnclosingRequestBase enc = (HttpEntityEnclosingRequestBase) capt.getValue(); + UrlEncodedFormEntity ent = (UrlEncodedFormEntity) enc.getEntity(); + String string = IOUtils.toString(ent.getContent()); + ourLog.info(string); + assertEquals("name=james", string); + } + + @Test + public void testSearchWithAbsoluteUrl() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Bundle response = client + .search() + .byUrl("http://example.com/fhir/Patient?birthdate=le2012-01-22&birthdate=gt2011-01-01&_include=Patient%3Aorganization&_sort%3Aasc=birthdate&_sort%3Adesc=name&_count=123&_format=json") + .returnBundle(Bundle.class) + .execute(); + + assertEquals(1, response.getEntry().size()); + } + + @SuppressWarnings("unused") + @Test + public void testSearchWithClientEncodingAndPrettyPrintConfig() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + client.setPrettyPrint(true); + client.setEncoding(EncodingEnum.JSON); - } + Bundle response = client.search() + .forResource(Patient.class) + .returnBundle(Bundle.class) + .execute(); - @SuppressWarnings("unused") - @Test - public void testSearchByTag() throws Exception { + assertEquals("http://example.com/fhir/Patient?_format=json&_pretty=true", capt.getValue().getURI().toString()); + + } + + @SuppressWarnings("unused") + @Test + public void testSearchWithEscapedParameters() throws Exception { - String msg = getPatientFeedWithOneResult(); + String msg = getPatientFeedWithOneResult(); - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - Bundle response = client.search() - .forResource(Patient.class) - .withTag("urn:foo", "123") - .withTag("urn:bar", "456") - .returnBundle(Bundle.class) - .execute(); - - assertEquals("http://example.com/fhir/Patient?_tag=urn%3Afoo%7C123&_tag=urn%3Abar%7C456", capt.getValue().getURI().toString()); - - } - - @SuppressWarnings("unused") - @Test - public void testSearchByToken() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - Bundle response = client.search() - .forResource("Patient") - .where(Patient.IDENTIFIER.exactly().systemAndCode("http://example.com/fhir", "ZZZ")) - .returnBundle(Bundle.class) - .execute(); - - assertEquals("http://example.com/fhir/Patient?identifier=http%3A%2F%2Fexample.com%2Ffhir%7CZZZ", capt.getValue().getURI().toString()); - - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - response = client.search() - .forResource("Patient") - .where(Patient.IDENTIFIER.exactly().code("ZZZ")) - .returnBundle(Bundle.class) - .execute(); - - assertEquals("http://example.com/fhir/Patient?identifier=ZZZ", capt.getAllValues().get(1).getURI().toString()); - - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - response = client.search() - .forResource("Patient") - .where(Patient.IDENTIFIER.exactly().codings(new Coding("A", "B", "ZZZ"), new Coding("C", "D", "ZZZ"))) - .returnBundle(Bundle.class) - .execute(); - - assertEquals("http://example.com/fhir/Patient?identifier=" + UrlUtil.escapeUrlParam("A|B,C|D"), capt.getAllValues().get(2).getURI().toString()); - - } - - /** - * Test for #192 - */ - @SuppressWarnings("unused") - @Test - public void testSearchByTokenWithEscaping() throws Exception { - final String msg = getPatientFeedWithOneResult(); - - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() { - @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { - return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); - } - }); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://foo"); - int index = 0; - String wantPrefix = "http://foo/Patient?identifier="; - - Bundle response = client.search() - .forResource("Patient") - .where(Patient.IDENTIFIER.exactly().systemAndCode("1", "2")) - .returnBundle(Bundle.class) - .execute(); - String wantValue = "1|2"; - String url = capt.getAllValues().get(index).getURI().toString(); - assertThat(url, Matchers.startsWith(wantPrefix)); - assertEquals(wantValue, UrlUtil.unescape(url.substring(wantPrefix.length()))); - assertEquals(UrlUtil.escapeUrlParam(wantValue), url.substring(wantPrefix.length())); - index++; - - response = client.search() - .forResource("Patient") - .where(Patient.IDENTIFIER.exactly().systemAndCode("1,2", "3,4")) - .returnBundle(Bundle.class) - .execute(); - wantValue = "1\\,2|3\\,4"; - url = capt.getAllValues().get(index).getURI().toString(); - assertThat(url, Matchers.startsWith(wantPrefix)); - assertEquals(wantValue, UrlUtil.unescape(url.substring(wantPrefix.length()))); - assertEquals(UrlUtil.escapeUrlParam(wantValue), url.substring(wantPrefix.length())); - index++; - } - - @SuppressWarnings("unused") - @Test - public void testSearchByTokenWithSystemAndNoCode() throws Exception { - - final String msg = getPatientFeedWithOneResult(); - - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() { - @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { - return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); - } - }); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - int idx = 0; - - Bundle response = client.search() - .forResource("Patient") - .where(Patient.IDENTIFIER.hasSystemWithAnyCode("urn:foo")) - .returnBundle(Bundle.class) - .execute(); - assertEquals("http://example.com/fhir/Patient?identifier=urn%3Afoo%7C", capt.getAllValues().get(idx++).getURI().toString()); - - response = client.search() - .forResource("Patient") - .where(Patient.IDENTIFIER.exactly().systemAndCode("urn:foo", null)) - .returnBundle(Bundle.class) - .execute(); - assertEquals("http://example.com/fhir/Patient?identifier=urn%3Afoo%7C", capt.getAllValues().get(idx++).getURI().toString()); - - response = client.search() - .forResource("Patient") - .where(Patient.IDENTIFIER.exactly().systemAndCode("urn:foo", "")) - .returnBundle(Bundle.class) - .execute(); - assertEquals("http://example.com/fhir/Patient?identifier=urn%3Afoo%7C", capt.getAllValues().get(idx++).getURI().toString()); - } - - @SuppressWarnings("unused") - @Test - public void testSearchIncludeRecursive() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - Bundle response = client.search() - .forResource(Patient.class) - .include(Patient.INCLUDE_ORGANIZATION) - .include(Patient.INCLUDE_LINK.asRecursive()) - .include(Patient.INCLUDE_ALL.asNonRecursive()) - .returnBundle(Bundle.class) - .execute(); - - assertThat(capt.getValue().getURI().toString(), containsString("http://example.com/fhir/Patient?")); - assertThat(capt.getValue().getURI().toString(), containsString("_include=" + UrlUtil.escapeUrlParam(Patient.INCLUDE_ORGANIZATION.getValue()))); - assertThat(capt.getValue().getURI().toString(), containsString("_include%3Arecurse=" + UrlUtil.escapeUrlParam(Patient.INCLUDE_LINK.getValue()))); - assertThat(capt.getValue().getURI().toString(), containsString("_include=*")); - - } - - @SuppressWarnings("unused") - @Test - public void testSearchUsingGetSearch() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - Bundle response = client.search() - .forResource("Patient") - .where(Patient.NAME.matches().value("james")) - .usingStyle(SearchStyleEnum.GET_WITH_SEARCH) - .returnBundle(Bundle.class) - .execute(); - - assertEquals("http://example.com/fhir/Patient/_search?name=james", capt.getValue().getURI().toString()); - } - - @SuppressWarnings("unused") - @Test - public void testSearchUsingPost() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - Bundle response = client.search() - .forResource("Patient") - .where(Patient.NAME.matches().value("james")) - .usingStyle(SearchStyleEnum.POST) - .returnBundle(Bundle.class) - .execute(); - - assertEquals("http://example.com/fhir/Patient/_search", capt.getValue().getURI().toString()); - - HttpEntityEnclosingRequestBase enc = (HttpEntityEnclosingRequestBase) capt.getValue(); - UrlEncodedFormEntity ent = (UrlEncodedFormEntity) enc.getEntity(); - String string = IOUtils.toString(ent.getContent()); - ourLog.info(string); - assertEquals("name=james", string); - } - - @Test - public void testSearchWithAbsoluteUrl() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - Bundle response = client - .search() - .byUrl("http://example.com/fhir/Patient?birthdate=le2012-01-22&birthdate=gt2011-01-01&_include=Patient%3Aorganization&_sort%3Aasc=birthdate&_sort%3Adesc=name&_count=123&_format=json") - .returnBundle(Bundle.class) - .execute(); - - assertEquals(1, response.getEntry().size()); - } - - @SuppressWarnings("unused") - @Test - public void testSearchWithClientEncodingAndPrettyPrintConfig() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - client.setPrettyPrint(true); - client.setEncoding(EncodingEnum.JSON); + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - Bundle response = client.search() - .forResource(Patient.class) - .returnBundle(Bundle.class) - .execute(); + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Bundle response = client.search() + .forResource("Patient") + .where(Patient.NAME.matches().values("NE,NE", "NE,NE")) + .where(Patient.NAME.matchesExactly().values("E$E")) + .where(Patient.NAME.matches().values("NE\\NE")) + .where(Patient.NAME.matchesExactly().values("E|E")) + .returnBundle(Bundle.class) + .execute(); - assertEquals("http://example.com/fhir/Patient?_format=json&_pretty=true", capt.getValue().getURI().toString()); - - } - - @SuppressWarnings("unused") - @Test - public void testSearchWithEscapedParameters() throws Exception { + assertThat(capt.getValue().getURI().toString(), containsString("%3A")); + assertEquals("http://example.com/fhir/Patient?name=NE\\,NE,NE\\,NE&name=NE\\\\NE&name:exact=E\\$E&name:exact=E\\|E", UrlUtil.unescape(capt.getValue().getURI().toString())); + } - String msg = getPatientFeedWithOneResult(); + @SuppressWarnings("unused") + @Test + public void testSearchWithInternalServerError() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + 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), 500, "INTERNAL ERRORS")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("Server Issues!"), Charset.forName("UTF-8"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + try { + client + .search() + .forResource(Patient.class) + .returnBundle(Bundle.class) + .execute(); + fail(); + } catch (InternalErrorException e) { + assertEquals(e.getMessage(), "HTTP 500 INTERNAL ERRORS: Server Issues!"); + assertEquals(e.getResponseBody(), "Server Issues!"); + } - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + } - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - Bundle response = client.search() - .forResource("Patient") - .where(Patient.NAME.matches().values("NE,NE", "NE,NE")) - .where(Patient.NAME.matchesExactly().values("E$E")) - .where(Patient.NAME.matches().values("NE\\NE")) - .where(Patient.NAME.matchesExactly().values("E|E")) - .returnBundle(Bundle.class) - .execute(); + @SuppressWarnings("unused") + @Test + public void testSearchWithNonFhirResponse() throws Exception { + + String msg = getPatientFeedWithOneResult(); - assertThat(capt.getValue().getURI().toString(), containsString("%3A")); - assertEquals("http://example.com/fhir/Patient?name=NE\\,NE,NE\\,NE&name=NE\\\\NE&name:exact=E\\$E&name:exact=E\\|E", UrlUtil.unescape(capt.getValue().getURI().toString())); - } + 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_TEXT + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("Server Issues!"), Charset.forName("UTF-8"))); - @SuppressWarnings("unused") - @Test - public void testSearchWithInternalServerError() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - 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), 500, "INTERNAL ERRORS")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("Server Issues!"), Charset.forName("UTF-8"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - try { - client - .search() - .forResource(Patient.class) - .returnBundle(Bundle.class) - .execute(); - fail(); - } catch (InternalErrorException e) { - assertEquals(e.getMessage(), "HTTP 500 INTERNAL ERRORS: Server Issues!"); - assertEquals(e.getResponseBody(), "Server Issues!"); - } + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - } + try { + client.search().forResource(Patient.class).returnBundle(Bundle.class).execute(); + fail(); + } catch (NonFhirResponseException e) { + assertThat(e.getMessage(), StringContains.containsString("Server Issues!")); + } - @SuppressWarnings("unused") - @Test - public void testSearchWithNonFhirResponse() throws Exception { - - String msg = getPatientFeedWithOneResult(); + } - 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_TEXT + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("Server Issues!"), Charset.forName("UTF-8"))); + @SuppressWarnings("unused") + @Test + public void testSearchWithReverseInclude() throws Exception { + + String msg = getPatientFeedWithOneResult(); - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - try { - client.search().forResource(Patient.class).returnBundle(Bundle.class).execute(); - fail(); - } catch (NonFhirResponseException e) { - assertThat(e.getMessage(), StringContains.containsString("Server Issues!")); - } + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - } + Bundle response = client.search() + .forResource(Patient.class) + .encodedJson() + .revInclude(Provenance.INCLUDE_TARGET) + .returnBundle(Bundle.class) + .execute(); - @SuppressWarnings("unused") - @Test - public void testSearchWithReverseInclude() throws Exception { - - String msg = getPatientFeedWithOneResult(); + assertEquals("http://example.com/fhir/Patient?_revinclude=Provenance%3Atarget&_format=json", capt.getValue().getURI().toString()); - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + } - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + @Test + public void testSetDefaultEncoding() throws Exception { + + String msg = ourCtx.newJsonParser().encodeResourceToString(new Patient()); + + 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(msg), Charset.forName("UTF-8"))); + // 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"), + // new BasicHeader(Constants.HEADER_CATEGORY, "http://foo/tagdefinition.html; scheme=\"http://hl7.org/fhir/tag\"; + // label=\"Some tag\"") }; + // when(myHttpResponse.getAllHeaders()).thenReturn(headers); - Bundle response = client.search() - .forResource(Patient.class) - .encodedJson() - .revInclude(Provenance.INCLUDE_TARGET) - .returnBundle(Bundle.class) - .execute(); + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - assertEquals("http://example.com/fhir/Patient?_revinclude=Provenance%3Atarget&_format=json", capt.getValue().getURI().toString()); + (client).setEncoding(EncodingEnum.JSON); + int count = 0; - } + client + .read() + .resource(Patient.class) + .withId(new IdType("Patient/1234")) + .execute(); + assertEquals("http://example.com/fhir/Patient/1234?_format=json", capt.getAllValues().get(count).getURI().toString()); + count++; - @Test - public void testSetDefaultEncoding() throws Exception { - - String msg = ourCtx.newJsonParser().encodeResourceToString(new Patient()); - - 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(msg), Charset.forName("UTF-8"))); - // 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"), - // new BasicHeader(Constants.HEADER_CATEGORY, "http://foo/tagdefinition.html; scheme=\"http://hl7.org/fhir/tag\"; - // label=\"Some tag\"") }; - // when(myHttpResponse.getAllHeaders()).thenReturn(headers); + } - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + @Test + public void testTransaction() throws Exception { + Bundle input = createTransactionBundleInput(); + Bundle output = createTransactionBundleOutput(); - (client).setEncoding(EncodingEnum.JSON); - int count = 0; + String msg = ourCtx.newJsonParser().encodeResourceToString(output); + + 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(msg), Charset.forName("UTF-8"))); - client - .read() - .resource(Patient.class) - .withId(new IdType("Patient/1234")) - .execute(); - assertEquals("http://example.com/fhir/Patient/1234?_format=json", capt.getAllValues().get(count).getURI().toString()); - count++; + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - } + Bundle response = client.transaction() + .withBundle(input) + .execute(); - @Test - public void testTransaction() throws Exception { - Bundle input = createTransactionBundleInput(); - Bundle output = createTransactionBundleOutput(); + assertEquals("http://example.com/fhir", capt.getValue().getURI().toString()); + assertEquals(input.getEntry().get(0).getResource().getId(), response.getEntry().get(0).getResource().getId()); + assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(0).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); - String msg = ourCtx.newJsonParser().encodeResourceToString(output); - - 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(msg), Charset.forName("UTF-8"))); + } - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + @Test + public void testTransactionXml() throws Exception { + Bundle input = createTransactionBundleInput(); + Bundle output = createTransactionBundleOutput(); - Bundle response = client.transaction() - .withBundle(input) - .execute(); + String msg = ourCtx.newXmlParser().encodeResourceToString(output); - assertEquals("http://example.com/fhir", capt.getValue().getURI().toString()); - assertEquals(input.getEntry().get(0).getResource().getId(), response.getEntry().get(0).getResource().getId()); - assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(0).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + 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_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - } + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - @Test - public void testTransactionXml() throws Exception { - Bundle input = createTransactionBundleInput(); - Bundle output = createTransactionBundleOutput(); + Bundle response = client.transaction() + .withBundle(input) + .execute(); - String msg = ourCtx.newXmlParser().encodeResourceToString(output); + assertEquals("http://example.com/fhir", capt.getValue().getURI().toString()); + assertEquals(input.getEntry().get(0).getResource().getId(), response.getEntry().get(0).getResource().getId()); + assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(0).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + + } + + @Test + public void testUpdate() throws Exception { + + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("foo:bar").setValue("12345"); + p1.addName().setFamily("Smith").addGiven("John"); + + 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.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"))); - 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_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - Bundle response = client.transaction() - .withBundle(input) - .execute(); - - assertEquals("http://example.com/fhir", capt.getValue().getURI().toString()); - assertEquals(input.getEntry().get(0).getResource().getId(), response.getEntry().get(0).getResource().getId()); - assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(0).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); - - } - - @Test - public void testUpdate() throws Exception { - - Patient p1 = new Patient(); - p1.addIdentifier().setSystem("foo:bar").setValue("12345"); - p1.addName().setFamily("Smith").addGiven("John"); - - 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.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"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - try { - client.update().resource(p1).execute(); - fail(); - } catch (InvalidRequestException e) { - // should happen because no ID set - } - - assertEquals(0, capt.getAllValues().size()); - - p1.setId("44"); - client.update().resource(p1).execute(); - - int count = 0; - - assertEquals(1, capt.getAllValues().size()); - assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); - count++; - - MethodOutcome outcome = client.update().resource(p1).execute(); - assertEquals("44", outcome.getId().getIdPart()); - assertEquals("22", outcome.getId().getVersionIdPart()); - - assertEquals(2, capt.getAllValues().size()); - - assertEquals("http://example.com/fhir/Patient/44", capt.getValue().getURI().toString()); - assertEquals("PUT", capt.getValue().getMethod()); - - /* - * Try fluent options - */ - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); - client.update().resource(p1).withId("123").execute(); - assertEquals(3, capt.getAllValues().size()); - assertEquals("http://example.com/fhir/Patient/123", capt.getAllValues().get(2).getURI().toString()); - - String resourceText = "<Patient xmlns=\"http://hl7.org/fhir\"> </Patient>"; - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); - client.update().resource(resourceText).withId("123").execute(); - assertEquals("http://example.com/fhir/Patient/123", capt.getAllValues().get(3).getURI().toString()); - assertEquals(resourceText, IOUtils.toString(((HttpPut) capt.getAllValues().get(3)).getEntity().getContent())); - assertEquals(4, capt.getAllValues().size()); - - } - - @Test - public void testUpdateWithStringAutoDetectsEncoding() throws Exception { - - Patient p1 = new Patient(); - p1.addIdentifier().setSystem("foo:bar").setValue("12345"); - p1.addName().setFamily("Smith").addGiven("John"); - - 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.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"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - int count = 0; - client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p1)).withId("1").execute(); - assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); - assertThat(extractBody(capt, count), containsString("value=\"John\"")); - count++; - - client.update().resource(ourCtx.newJsonParser().encodeResourceToString(p1)).withId("1").execute(); - assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.JSON.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); - assertThat(extractBody(capt, count), containsString("[\"John\"]")); - count++; - - /* - * e.g. Now try with reversed encoding (provide a string that's in JSON and ask the client to use XML) - */ - - client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p1)).withId("1").encodedJson().execute(); - assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.JSON.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); - assertThat(extractBody(capt, count), containsString("[\"John\"]")); - count++; - - client.update().resource(ourCtx.newJsonParser().encodeResourceToString(p1)).withId("1").encodedXml().execute(); - assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); - assertThat(extractBody(capt, count), containsString("value=\"John\"")); - count++; - } - - @Test - public void testVReadWithAbsoluteUrl() throws Exception { - - String msg = getResourceResult(); - - 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_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"), - new BasicHeader(Constants.HEADER_CONTENT_LOCATION, "http://foo.com/Patient/123/_history/2333"), - }; - when(myHttpResponse.getAllHeaders()).thenReturn(headers); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - Patient response = client - .read() - .resource(Patient.class) - .withUrl("http://somebase.com/path/to/base/Patient/1234/_history/2222") - .execute(); - - assertThat(response.getNameFirstRep().getFamily(), StringContains.containsString("Cardinal")); - assertEquals("http://somebase.com/path/to/base/Patient/1234/_history/2222", capt.getAllValues().get(0).getURI().toString()); - - } - - @Test - public void testValidateNonFluent() throws Exception { - - OperationOutcome oo = new OperationOutcome(); - oo.addIssue().setDiagnostics("OOOK"); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] {}); - 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(ourCtx.newXmlParser().encodeResourceToString(oo)), Charset.forName("UTF-8"))); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - Patient p1 = new Patient(); - p1.addIdentifier().setSystem("foo:bar").setValue("12345"); - p1.addName().setFamily("Smith").addGiven("John"); - - MethodOutcome resp = client.validate(p1); - assertEquals("http://example.com/fhir/Patient/$validate", capt.getValue().getURI().toString()); - oo = (OperationOutcome) resp.getOperationOutcome(); - assertEquals("OOOK", oo.getIssueFirstRep().getDiagnostics()); - - } - - @AfterClass - public static void afterClassClearContext() { - TestUtil.clearAllStaticFieldsForUnitTest(); - } - - @BeforeClass - public static void beforeClass() { - ourCtx = FhirContext.forR4(); - } + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + try { + client.update().resource(p1).execute(); + fail(); + } catch (InvalidRequestException e) { + // should happen because no ID set + } + + assertEquals(0, capt.getAllValues().size()); + + p1.setId("44"); + client.update().resource(p1).execute(); + + int count = 0; + + assertEquals(1, capt.getAllValues().size()); + assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); + assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + count++; + + MethodOutcome outcome = client.update().resource(p1).execute(); + assertEquals("44", outcome.getId().getIdPart()); + assertEquals("22", outcome.getId().getVersionIdPart()); + + assertEquals(2, capt.getAllValues().size()); + + assertEquals("http://example.com/fhir/Patient/44", capt.getValue().getURI().toString()); + assertEquals("PUT", capt.getValue().getMethod()); + + /* + * Try fluent options + */ + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); + client.update().resource(p1).withId("123").execute(); + assertEquals(3, capt.getAllValues().size()); + assertEquals("http://example.com/fhir/Patient/123", capt.getAllValues().get(2).getURI().toString()); + + String resourceText = "<Patient xmlns=\"http://hl7.org/fhir\"> </Patient>"; + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); + client.update().resource(resourceText).withId("123").execute(); + assertEquals("http://example.com/fhir/Patient/123", capt.getAllValues().get(3).getURI().toString()); + assertEquals(resourceText, IOUtils.toString(((HttpPut) capt.getAllValues().get(3)).getEntity().getContent())); + assertEquals(4, capt.getAllValues().size()); + + } + + @Test + public void testUpdateWithStringAutoDetectsEncoding() throws Exception { + + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("foo:bar").setValue("12345"); + p1.addName().setFamily("Smith").addGiven("John"); + + 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.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"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + int count = 0; + client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p1)).withId("1").execute(); + assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); + assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertThat(extractBody(capt, count), containsString("value=\"John\"")); + count++; + + client.update().resource(ourCtx.newJsonParser().encodeResourceToString(p1)).withId("1").execute(); + assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); + assertEquals(EncodingEnum.JSON.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertThat(extractBody(capt, count), containsString("[\"John\"]")); + count++; + + /* + * e.g. Now try with reversed encoding (provide a string that's in JSON and ask the client to use XML) + */ + + client.update().resource(ourCtx.newXmlParser().encodeResourceToString(p1)).withId("1").encodedJson().execute(); + assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); + assertEquals(EncodingEnum.JSON.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertThat(extractBody(capt, count), containsString("[\"John\"]")); + count++; + + client.update().resource(ourCtx.newJsonParser().encodeResourceToString(p1)).withId("1").encodedXml().execute(); + assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); + assertEquals(EncodingEnum.XML.getResourceContentTypeNonLegacy() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertThat(extractBody(capt, count), containsString("value=\"John\"")); + count++; + } + + @Test + public void testVReadWithAbsoluteUrl() throws Exception { + + String msg = getResourceResult(); + + 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_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"), + new BasicHeader(Constants.HEADER_CONTENT_LOCATION, "http://foo.com/Patient/123/_history/2333"), + }; + when(myHttpResponse.getAllHeaders()).thenReturn(headers); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Patient response = client + .read() + .resource(Patient.class) + .withUrl("http://somebase.com/path/to/base/Patient/1234/_history/2222") + .execute(); + + assertThat(response.getNameFirstRep().getFamily(), StringContains.containsString("Cardinal")); + assertEquals("http://somebase.com/path/to/base/Patient/1234/_history/2222", capt.getAllValues().get(0).getURI().toString()); + + } + + @Test + public void testValidateNonFluent() throws Exception { + + OperationOutcome oo = new OperationOutcome(); + oo.addIssue().setDiagnostics("OOOK"); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] {}); + 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(ourCtx.newXmlParser().encodeResourceToString(oo)), Charset.forName("UTF-8"))); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("foo:bar").setValue("12345"); + p1.addName().setFamily("Smith").addGiven("John"); + + MethodOutcome resp = client.validate(p1); + assertEquals("http://example.com/fhir/Patient/$validate", capt.getValue().getURI().toString()); + oo = (OperationOutcome) resp.getOperationOutcome(); + assertEquals("OOOK", oo.getIssueFirstRep().getDiagnostics()); + + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + @BeforeClass + public static void beforeClass() { + ourCtx = FhirContext.forR4(); + } } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java index 33ad4feabb9..5665c8be695 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java @@ -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 { diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/InjectionAttackTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/InjectionAttackTest.java new file mode 100644 index 00000000000..4d0c63f3727 --- /dev/null +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/InjectionAttackTest.java @@ -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<>(); + } + + + } + +} diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerWithResponseHighlightingInterceptorExceptionTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ServerWithResponseHighlightingInterceptorExceptionTest.java similarity index 86% rename from hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerWithResponseHighlightingInterceptorExceptionTest.java rename to hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ServerWithResponseHighlightingInterceptorExceptionTest.java index 72fb556250d..0a8f2f0dd5e 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerWithResponseHighlightingInterceptorExceptionTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ServerWithResponseHighlightingInterceptorExceptionTest.java @@ -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; @@ -49,7 +46,7 @@ public class ServerWithResponseHighlightingInterceptorExceptionTest { String responseContent = IOUtils.toString(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent()); ourLog.info(responseContent); - + assertEquals(400, status.getStatusLine().getStatusCode()); assertThat(responseContent, containsString("<diagnostics value=\"AAABBB\"/>")); } @@ -62,12 +59,11 @@ public class ServerWithResponseHighlightingInterceptorExceptionTest { String responseContent = IOUtils.toString(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent()); ourLog.info(responseContent); - + assertEquals(500, status.getStatusLine().getStatusCode()); 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"); } + } } diff --git a/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm b/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm index 4100bb95336..9011383ae8e 100644 --- a/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm +++ b/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm @@ -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}> diff --git a/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans_java.vm b/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans_java.vm index 36199d29dd6..bad24d8db58 100644 --- a/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans_java.vm +++ b/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans_java.vm @@ -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 ) - retVal.add(rp${res.declaringClassNameComplete}${versionCapitalized}()); + 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 ) - retVal.add(dao${res.declaringClassNameComplete}${versionCapitalized}()); + 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}>(); diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 6f30cebad09..995fb40d82e 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -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. " and <) + 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">