From b4a362b8eeca42fc96fd68c5b6f48ea98d1403db Mon Sep 17 00:00:00 2001 From: James Date: Wed, 1 Feb 2017 05:55:35 -0500 Subject: [PATCH 1/7] Work in progress --- .../.settings/org.eclipse.jdt.core.prefs | 5 + .../.settings/org.eclipse.jdt.core.prefs | 5 + .../java/ca/uhn/fhir/context/FhirContext.java | 172 ++++++------ .../ca/uhn/fhir/context/ModelScanner.java | 5 +- .../uhn/fhir/context/RuntimeSearchParam.java | 18 +- .../fhir/rest/annotation/OptionalParam.java | 126 ++++----- .../ca/uhn/fhir/rest/annotation/RawParam.java | 20 ++ .../fhir/rest/annotation/RequiredParam.java | 63 +++-- .../ca/uhn/fhir/rest/method/MethodUtil.java | 2 + .../fhir/rest/method/RawParamsParmeter.java | 74 +++++ .../fhir/rest/method/SearchMethodBinding.java | 19 +- .../.settings/org.eclipse.jdt.core.prefs | 5 + .../.settings/org.eclipse.jdt.core.prefs | 5 + .../.settings/org.eclipse.jdt.core.prefs | 5 + .../.settings/org.eclipse.jdt.core.prefs | 5 + .../uhn/fhir/jpa/config/BaseDstu1Config.java | 36 ++- .../uhn/fhir/jpa/config/BaseDstu2Config.java | 7 + .../jpa/config/dstu3/BaseDstu3Config.java | 58 ++-- .../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 7 +- .../jpa/dao/BaseSearchParamExtractor.java | 51 ++-- .../fhir/jpa/dao/BaseSearchParamRegistry.java | 65 +++++ .../java/ca/uhn/fhir/jpa/dao/DaoConfig.java | 120 +++++--- .../jpa/dao/FhirResourceDaoPatientDstu2.java | 6 +- .../fhir/jpa/dao/ISearchParamRegistry.java | 13 + .../ca/uhn/fhir/jpa/dao/SearchBuilder.java | 91 +++--- .../jpa/dao/SearchParamExtractorDstu1.java | 24 +- .../jpa/dao/SearchParamExtractorDstu2.java | 44 +-- .../jpa/dao/SearchParamRegistryDstu1.java | 6 + .../jpa/dao/SearchParamRegistryDstu2.java | 6 + .../dstu3/FhirResourceDaoPatientDstu3.java | 7 +- .../dao/dstu3/SearchParamExtractorDstu3.java | 68 ++--- .../dao/dstu3/SearchParamRegistryDstu3.java | 194 +++++++++++++ .../fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java | 6 + ...ResourceDaoDstu3SearchCustomParamTest.java | 203 ++++++++++++++ .../dstu3/SearchParamExtractorDstu3Test.java | 26 +- .../fhir/rest/server/DynamicSearchTest.java | 5 +- .../server/SearchDefaultMethodDstu3Test.java | 261 ++++++++++++++++++ .../.settings/org.eclipse.jdt.core.prefs | 5 + 38 files changed, 1448 insertions(+), 390 deletions(-) create mode 100644 example-projects/hapi-fhir-base-example-embedded-ws/.settings/org.eclipse.jdt.core.prefs create mode 100644 example-projects/hapi-fhir-standalone-overlay-example/.settings/org.eclipse.jdt.core.prefs create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/RawParam.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RawParamsParmeter.java create mode 100644 hapi-fhir-client-okhttp/.settings/org.eclipse.jdt.core.prefs create mode 100644 hapi-fhir-jacoco/.settings/org.eclipse.jdt.core.prefs create mode 100644 hapi-fhir-jaxrsserver-base/.settings/org.eclipse.jdt.core.prefs create mode 100644 hapi-fhir-jaxrsserver-example/.settings/org.eclipse.jdt.core.prefs create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamRegistry.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchParamRegistry.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamRegistryDstu1.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamRegistryDstu2.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/SearchParamRegistryDstu3.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchCustomParamTest.java create mode 100644 hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/SearchDefaultMethodDstu3Test.java create mode 100644 hapi-fhir-validation-resources-dstu2.1/.settings/org.eclipse.jdt.core.prefs diff --git a/example-projects/hapi-fhir-base-example-embedded-ws/.settings/org.eclipse.jdt.core.prefs b/example-projects/hapi-fhir-base-example-embedded-ws/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..60105c1b951 --- /dev/null +++ b/example-projects/hapi-fhir-base-example-embedded-ws/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/example-projects/hapi-fhir-standalone-overlay-example/.settings/org.eclipse.jdt.core.prefs b/example-projects/hapi-fhir-standalone-overlay-example/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..714351aec19 --- /dev/null +++ b/example-projects/hapi-fhir-standalone-overlay-example/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.8 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 edcbecb4ea6..8e60f760bb5 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 @@ -82,87 +82,22 @@ public class FhirContext { private Map> myDefaultTypeForProfile = new HashMap>(); private volatile Map myIdToResourceDefinition = Collections.emptyMap(); private boolean myInitialized; + private boolean myInitializing; private HapiLocalizer myLocalizer = new HapiLocalizer(); private volatile Map> myNameToElementDefinition = Collections.emptyMap(); private volatile Map myNameToResourceDefinition = Collections.emptyMap(); private volatile Map> myNameToResourceType; private volatile INarrativeGenerator myNarrativeGenerator; private volatile IParserErrorHandler myParserErrorHandler = new LenientErrorHandler(); + private ParserOptions myParserOptions = new ParserOptions(); private Set myPerformanceOptions = new HashSet(); private Collection> myResourceTypesToScan; private volatile IRestfulClientFactory myRestfulClientFactory; private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition; - private final IFhirVersion myVersion; - private Map>> myVersionToNameToResourceType = Collections.emptyMap(); - private boolean myInitializing; private IContextValidationSupport myValidationSupport; + private final IFhirVersion myVersion; - /** - * 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() { - if (myValidationSupport == null) { - myValidationSupport = myVersion.createValidationSupport(); - } - return myValidationSupport; - } - - /** - * Creates a new FluentPath engine which can be used to exvaluate - * path expressions over FHIR resources. Note that this engine will use the - * {@link IContextValidationSupport context validation support} module which is - * configured on the context at the time this method is called. - *

- * In other words, call {@link #setValidationSupport(IContextValidationSupport)} before - * calling {@link #newFluentPath()} - *

- *

- * Note that this feature was added for FHIR DSTU3 and is not available - * for contexts configured to use an older version of FHIR. Calling this method - * on a context for a previous version of fhir will result in an - * {@link UnsupportedOperationException} - *

- * - * @since 2.2 - */ - public IFluentPath newFluentPath() { - return myVersion.createFluentPathExecutor(this); - } - - /** - * 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; - } - - private ParserOptions myParserOptions = new ParserOptions(); - - /** - * 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; - } + private Map>> myVersionToNameToResourceType = Collections.emptyMap(); /** * @deprecated It is recommended that you use one of the static initializer methods instead @@ -172,7 +107,7 @@ public class FhirContext { public FhirContext() { this(EMPTY_LIST); } - + /** * @deprecated It is recommended that you use one of the static initializer methods instead * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} @@ -282,7 +217,7 @@ public class FhirContext { validateInitialized(); return myNameToResourceDefinition.values(); } - + /** * Returns the default resource type for the given profile * @@ -332,7 +267,7 @@ public class FhirContext { validateInitialized(); return Collections.unmodifiableCollection(myClassToElementDefinition.values()); } - + /** * This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with * caution @@ -348,6 +283,16 @@ public class FhirContext { return myNarrativeGenerator; } + /** + * 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; + } + /** * Get the configured performance options */ @@ -449,6 +394,20 @@ public class FhirContext { return myIdToResourceDefinition.get(theId); } +// /** +// * 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 models. This is an advanced feature which is generally only needed for extending the * core library. @@ -476,6 +435,19 @@ public class FhirContext { return myRuntimeChildUndeclaredExtensionDefinition; } + /** + * 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() { + if (myValidationSupport == null) { + myValidationSupport = myVersion.createValidationSupport(); + } + return myValidationSupport; + } + public IFhirVersion getVersion() { return myVersion; } @@ -500,6 +472,28 @@ public class FhirContext { return myVersion.newBundleFactory(this); } + /** + * Creates a new FluentPath engine which can be used to exvaluate + * path expressions over FHIR resources. Note that this engine will use the + * {@link IContextValidationSupport context validation support} module which is + * configured on the context at the time this method is called. + *

+ * In other words, call {@link #setValidationSupport(IContextValidationSupport)} before + * calling {@link #newFluentPath()} + *

+ *

+ * Note that this feature was added for FHIR DSTU3 and is not available + * for contexts configured to use an older version of FHIR. Calling this method + * on a context for a previous version of fhir will result in an + * {@link UnsupportedOperationException} + *

+ * + * @since 2.2 + */ + public IFluentPath newFluentPath() { + return myVersion.createFluentPathExecutor(this); + } + /** * Create and return a new JSON parser. * @@ -783,6 +777,17 @@ public class FhirContext { 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 * @@ -818,6 +823,15 @@ public class FhirContext { 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" }) private List> toElementList(Collection> theResourceTypes) { if (theResourceTypes == null) { @@ -850,6 +864,13 @@ 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) @@ -885,11 +906,4 @@ public class FhirContext { return retVal; } - /** - * 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); - } - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java index 42776441e9a..96a418a7c95 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java @@ -34,6 +34,7 @@ import java.util.Map.Entry; import org.apache.commons.io.IOUtils; import org.hl7.fhir.instance.model.api.*; +import ca.uhn.fhir.context.RuntimeSearchParam.RuntimeSearchParamStatusEnum; import ca.uhn.fhir.model.api.ExtensionDt; import ca.uhn.fhir.model.api.IDatatype; import ca.uhn.fhir.model.api.IElement; @@ -437,7 +438,7 @@ class ModelScanner { } - RuntimeSearchParam param = new RuntimeSearchParam(searchParam.name(), searchParam.description(), searchParam.path(), paramType, providesMembershipInCompartments, toTargetList(searchParam.target())); + RuntimeSearchParam param = new RuntimeSearchParam(searchParam.name(), searchParam.description(), searchParam.path(), paramType, providesMembershipInCompartments, toTargetList(searchParam.target()), RuntimeSearchParamStatusEnum.ACTIVE); theResourceDef.addSearchParam(param); nameToParam.put(param.getName(), param); } @@ -457,7 +458,7 @@ class ModelScanner { compositeOf.add(param); } - RuntimeSearchParam param = new RuntimeSearchParam(searchParam.name(), searchParam.description(), searchParam.path(), RestSearchParameterTypeEnum.COMPOSITE, compositeOf, null, toTargetList(searchParam.target())); + RuntimeSearchParam param = new RuntimeSearchParam(searchParam.name(), searchParam.description(), searchParam.path(), RestSearchParameterTypeEnum.COMPOSITE, compositeOf, null, toTargetList(searchParam.target()), RuntimeSearchParamStatusEnum.ACTIVE); theResourceDef.addSearchParam(param); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java index 22da3f10e0f..7851ef55d6b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java @@ -37,15 +37,17 @@ public class RuntimeSearchParam { private final String myPath; private final Set myTargets; private final Set myProvidesMembershipInCompartments; + private final RuntimeSearchParamStatusEnum myStatus; public RuntimeSearchParam(String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, List theCompositeOf, - Set theProvidesMembershipInCompartments, Set theTargets) { + Set theProvidesMembershipInCompartments, Set theTargets, RuntimeSearchParamStatusEnum theStatus) { super(); myName = theName; myDescription = theDescription; myPath = thePath; myParamType = theParamType; myCompositeOf = theCompositeOf; + myStatus = theStatus; if (theProvidesMembershipInCompartments != null && !theProvidesMembershipInCompartments.isEmpty()) { myProvidesMembershipInCompartments = Collections.unmodifiableSet(theProvidesMembershipInCompartments); } else { @@ -62,8 +64,12 @@ public class RuntimeSearchParam { return myTargets; } - public RuntimeSearchParam(String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, Set theProvidesMembershipInCompartments, Set theTargets) { - this(theName, theDescription, thePath, theParamType, null, theProvidesMembershipInCompartments, theTargets); + public RuntimeSearchParamStatusEnum getStatus() { + return myStatus; + } + + public RuntimeSearchParam(String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, Set theProvidesMembershipInCompartments, Set theTargets, RuntimeSearchParamStatusEnum theStatus) { + this(theName, theDescription, thePath, theParamType, null, theProvidesMembershipInCompartments, theTargets, theStatus); } public List getCompositeOf() { @@ -108,4 +114,10 @@ public class RuntimeSearchParam { return myProvidesMembershipInCompartments; } + public enum RuntimeSearchParamStatusEnum { + ACTIVE, + DRAFT, + RETIRED + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/OptionalParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/OptionalParam.java index 5a1b573b030..6b7bf8a3df6 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/OptionalParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/OptionalParam.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.rest.annotation; +import java.lang.annotation.ElementType; + /* * #%L * HAPI FHIR - Core Library @@ -10,7 +12,7 @@ package ca.uhn.fhir.rest.annotation; * 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 + * 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, @@ -22,19 +24,19 @@ package ca.uhn.fhir.rest.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.hl7.fhir.instance.model.api.IBaseResource; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.param.CompositeParam; import ca.uhn.fhir.rest.param.ReferenceParam; -//import ca.uhn.fhir.testmodel.Patient; // TODO: qualify this correctly - /** - * Parameter annotation which specifies a search parameter for a {@link Search} method. + * Parameter annotation which specifies a search parameter for a {@link Search} method. */ @Retention(RetentionPolicy.RUNTIME) +@Target(value=ElementType.PARAMETER) public @interface OptionalParam { public static final String ALLOW_CHAIN_ANY = "*"; @@ -42,63 +44,63 @@ public @interface OptionalParam { public static final String ALLOW_CHAIN_NOTCHAINED = ""; /** - * For reference parameters ({@link ReferenceParam}) this value may be - * used to indicate which chain values (if any) are not valid - * for the given parameter. Values here will supercede any values specified - * in {@link #chainWhitelist()} - *

- * If the parameter annotated with this annotation is not a {@link ReferenceParam}, - * this value must not be populated. - *

- */ + * For reference parameters ({@link ReferenceParam}) this value may be + * used to indicate which chain values (if any) are not valid + * for the given parameter. Values here will supercede any values specified + * in {@link #chainWhitelist()} + *

+ * If the parameter annotated with this annotation is not a {@link ReferenceParam}, + * this value must not be populated. + *

+ */ String[] chainBlacklist() default {}; - - /** - * For reference parameters ({@link ReferenceParam}) this value may be - * used to indicate which chain values (if any) are valid for the given - * parameter. If the list contains the value {@link #ALLOW_CHAIN_ANY}, all values are valid. (this is the default) - * If the list contains the value {@link #ALLOW_CHAIN_NOTCHAINED} - * then the reference param only supports the empty chain (i.e. the resource - * ID). - *

- * Valid values for this parameter include: - *

- *
    - *
  • chainWhitelist={ OptionalParam.ALLOW_CHAIN_NOTCHAINED } - Only allow resource reference (no chaining allowed for this parameter)
  • - *
  • chainWhitelist={ OptionalParam.ALLOW_CHAIN_ANY } - Allow any chaining at all (including a non chained value, this is the default)
  • - *
  • chainWhitelist={ "foo", "bar" } - Allow property.foo and property.bar
  • - *
- *

- * Any values specified in - * {@link #chainBlacklist()} will supercede (have priority over) values - * here. - *

- *

- * If the parameter annotated with this annotation is not a {@link ReferenceParam}, - * this value must not be populated. - *

- */ - String[] chainWhitelist() default {ALLOW_CHAIN_ANY}; - - /** - * For composite parameters ({@link CompositeParam}) this value may be - * used to indicate the parameter type(s) which may be referenced by this param. - *

- * If the parameter annotated with this annotation is not a {@link CompositeParam}, - * this value must not be populated. - *

- */ - Class[] compositeTypes() default {}; - /** - * This is the name for the parameter. Generally this should be a + /** + * For reference parameters ({@link ReferenceParam}) this value may be + * used to indicate which chain values (if any) are valid for the given + * parameter. If the list contains the value {@link #ALLOW_CHAIN_ANY}, all values are valid. (this is the default) + * If the list contains the value {@link #ALLOW_CHAIN_NOTCHAINED} + * then the reference param only supports the empty chain (i.e. the resource + * ID). + *

+ * Valid values for this parameter include: + *

+ *
    + *
  • chainWhitelist={ OptionalParam.ALLOW_CHAIN_NOTCHAINED } - Only allow resource reference (no chaining allowed for this parameter)
  • + *
  • chainWhitelist={ OptionalParam.ALLOW_CHAIN_ANY } - Allow any chaining at all (including a non chained value, this is the default)
  • + *
  • chainWhitelist={ "foo", "bar" } - Allow property.foo and property.bar
  • + *
+ *

+ * Any values specified in + * {@link #chainBlacklist()} will supercede (have priority over) values + * here. + *

+ *

+ * If the parameter annotated with this annotation is not a {@link ReferenceParam}, + * this value must not be populated. + *

+ */ + String[] chainWhitelist() default { ALLOW_CHAIN_ANY }; + + /** + * For composite parameters ({@link CompositeParam}) this value may be + * used to indicate the parameter type(s) which may be referenced by this param. + *

+ * If the parameter annotated with this annotation is not a {@link CompositeParam}, + * this value must not be populated. + *

+ */ + Class[] compositeTypes() default {}; + + /** + * This is the name for the parameter. Generally this should be a * simple string (e.g. "name", or "identifier") which will be the name * of the URL parameter used to populate this method parameter. *

* Most resource model classes have constants which may be used to * supply values for this field, e.g. Patient.SP_NAME or * Observation.SP_DATE - *

+ *

*

* If you wish to specify a parameter for a resource reference which * only accepts a specific chained value, it is also valid to supply @@ -109,13 +111,13 @@ public @interface OptionalParam { */ String name(); - /** - * For resource reference parameters ({@link ReferenceParam}) this value may be - * used to indicate the resource type(s) which may be referenced by this param. - *

- * If the parameter annotated with this annotation is not a {@link ReferenceParam}, - * this value must not be populated. - *

- */ - Class[] targetTypes() default {}; + /** + * For resource reference parameters ({@link ReferenceParam}) this value may be + * used to indicate the resource type(s) which may be referenced by this param. + *

+ * If the parameter annotated with this annotation is not a {@link ReferenceParam}, + * this value must not be populated. + *

+ */ + Class[] targetTypes() default {}; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/RawParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/RawParam.java new file mode 100644 index 00000000000..db68c28b50b --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/RawParam.java @@ -0,0 +1,20 @@ +package ca.uhn.fhir.rest.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * On a {@link Search} method, a parameter marked with this annotation + * will receive search parameters not captured by other parameters. + *

+ * Parameters with this annotation must be of type + * {@code Map>} + *

+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value=ElementType.PARAMETER) +public @interface RawParam { + // nothing +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/RequiredParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/RequiredParam.java index af12e9e14ac..9efda9da757 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/RequiredParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/RequiredParam.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.rest.annotation; +import java.lang.annotation.ElementType; + /* * #%L * HAPI FHIR - Core Library @@ -10,7 +12,7 @@ package ca.uhn.fhir.rest.annotation; * 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 + * 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, @@ -22,18 +24,19 @@ package ca.uhn.fhir.rest.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.hl7.fhir.instance.model.api.IBaseResource; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.param.CompositeParam; import ca.uhn.fhir.rest.param.ReferenceParam; -//import ca.uhn.fhir.testmodel.Patient; // TODO: qualify this correctly -@Retention(RetentionPolicy.RUNTIME) /** - * Parameter annotation which specifies a search parameter for a {@link Search} method. + * Parameter annotation which specifies a search parameter for a {@link Search} method. */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value=ElementType.PARAMETER) public @interface RequiredParam { /** @@ -45,32 +48,32 @@ public @interface RequiredParam { */ String[] chainBlacklist() default {}; - /** - * For reference parameters ({@link ReferenceParam}) this value may be - * used to indicate which chain values (if any) are valid for the given - * parameter. If the list contains the value {@link OptionalParam#ALLOW_CHAIN_ANY}, all values are valid. (this is the default) - * If the list contains the value {@link OptionalParam#ALLOW_CHAIN_NOTCHAINED} - * then the reference param only supports the empty chain (i.e. the resource - * ID). - *

- * Valid values for this parameter include: - *

- *
    - *
  • chainWhitelist={ OptionalParam.ALLOW_CHAIN_NOTCHAINED } - Only allow resource reference (no chaining allowed for this parameter)
  • - *
  • chainWhitelist={ OptionalParam.ALLOW_CHAIN_ANY } - Allow any chaining at all (including a non chained value, this is the default)
  • - *
  • chainWhitelist={ "foo", "bar" } - Allow property.foo and property.bar
  • - *
- *

- * Any values specified in - * {@link #chainBlacklist()} will supercede (have priority over) values - * here. - *

- *

- * If the parameter annotated with this annotation is not a {@link ReferenceParam}, - * this value must not be populated. - *

- */ - String[] chainWhitelist() default {OptionalParam.ALLOW_CHAIN_ANY}; + /** + * For reference parameters ({@link ReferenceParam}) this value may be + * used to indicate which chain values (if any) are valid for the given + * parameter. If the list contains the value {@link OptionalParam#ALLOW_CHAIN_ANY}, all values are valid. (this is the default) + * If the list contains the value {@link OptionalParam#ALLOW_CHAIN_NOTCHAINED} + * then the reference param only supports the empty chain (i.e. the resource + * ID). + *

+ * Valid values for this parameter include: + *

+ *
    + *
  • chainWhitelist={ OptionalParam.ALLOW_CHAIN_NOTCHAINED } - Only allow resource reference (no chaining allowed for this parameter)
  • + *
  • chainWhitelist={ OptionalParam.ALLOW_CHAIN_ANY } - Allow any chaining at all (including a non chained value, this is the default)
  • + *
  • chainWhitelist={ "foo", "bar" } - Allow property.foo and property.bar
  • + *
+ *

+ * Any values specified in + * {@link #chainBlacklist()} will supercede (have priority over) values + * here. + *

+ *

+ * If the parameter annotated with this annotation is not a {@link ReferenceParam}, + * this value must not be populated. + *

+ */ + String[] chainWhitelist() default { OptionalParam.ALLOW_CHAIN_ANY }; /** * For composite parameters ({@link CompositeParam}) this parameter may be used to indicate the parameter type(s) which may be referenced by this param. diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java index acf66dc7a1e..0a1ec69dbfd 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java @@ -407,6 +407,8 @@ public class MethodUtil { parameter.setType(theContext, parameterType, innerCollectionType, outerCollectionType); MethodUtil.extractDescription(parameter, annotations); param = parameter; + } else if (nextAnnotation instanceof RawParam) { + param = new RawParamsParmeter(parameters); } else if (nextAnnotation instanceof IncludeParam) { Class> instantiableCollectionType; Class specType; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RawParamsParmeter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RawParamsParmeter.java new file mode 100644 index 00000000000..91afa3afe88 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RawParamsParmeter.java @@ -0,0 +1,74 @@ +package ca.uhn.fhir.rest.method; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.annotation.RawParam; +import ca.uhn.fhir.rest.method.SearchMethodBinding.QualifierDetails; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; + +public class RawParamsParmeter implements IParameter { + + private final List myAllMethodParameters; + + public RawParamsParmeter(List theParameters) { + myAllMethodParameters = theParameters; + } + + @Override + public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map> theTargetQueryArguments, IBaseResource theTargetResource) + throws InternalErrorException { + // not supported on client for now + } + + @Override + public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { + HashMap> retVal = null; + + for (String nextName : theRequest.getParameters().keySet()) { + if (nextName.startsWith("_")) { + continue; + } + + QualifierDetails qualifiers = SearchMethodBinding.extractQualifiersFromParameterName(nextName); + + boolean alreadyCaptured = false; + for (IParameter nextParameter : myAllMethodParameters) { + if (nextParameter instanceof SearchParameter) { + SearchParameter nextSearchParam = (SearchParameter)nextParameter; + if (nextSearchParam.getName().equals(qualifiers.getParamName())) { + if (qualifiers.passes(nextSearchParam.getQualifierWhitelist(), nextSearchParam.getQualifierBlacklist())) { + alreadyCaptured = true; + break; + } + } + } + } + + if (!alreadyCaptured) { + if (retVal == null) { + retVal = new HashMap>(); + } + retVal.put(nextName, Arrays.asList(theRequest.getParameters().get(nextName))); + } + + } + + return retVal; + } + + @Override + public void initializeTypes(Method theMethod, Class> theOuterCollectionType, Class> theInnerCollectionType, Class theParameterType) { + Validate.isTrue(theParameterType.equals(Map.class), "Parameter with @" + RawParam.class + " must be of type Map>"); + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java index 9e364c4cbb3..456b24cf304 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java @@ -130,11 +130,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { @Override public ReturnTypeEnum getReturnType() { -// if (getContext().getVersion().getVersion() == FhirVersionEnum.DSTU1) { return ReturnTypeEnum.BUNDLE; -// } else { -// return ReturnTypeEnum.RESOURCE; -// } } @Override @@ -406,14 +402,20 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { if (dotIdx < colonIdx) { retVal.setDotQualifier(theParamName.substring(dotIdx, colonIdx)); retVal.setColonQualifier(theParamName.substring(colonIdx)); + retVal.setParamName(theParamName.substring(0, dotIdx)); } else { retVal.setColonQualifier(theParamName.substring(colonIdx, dotIdx)); retVal.setDotQualifier(theParamName.substring(dotIdx)); + retVal.setParamName(theParamName.substring(0, colonIdx)); } } else if (dotIdx != -1) { retVal.setDotQualifier(theParamName.substring(dotIdx)); + retVal.setParamName(theParamName.substring(0, dotIdx)); } else if (colonIdx != -1) { retVal.setColonQualifier(theParamName.substring(colonIdx)); + retVal.setParamName(theParamName.substring(0, colonIdx)); + } else { + retVal.setParamName(theParamName); } return retVal; @@ -423,6 +425,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { private String myColonQualifier; private String myDotQualifier; + private String myParamName; public boolean passes(Set theQualifierWhitelist, Set theQualifierBlacklist) { if (theQualifierWhitelist != null) { @@ -468,6 +471,14 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { return true; } + public void setParamName(String theParamName) { + myParamName = theParamName; + } + + public String getParamName() { + return myParamName; + } + public void setColonQualifier(String theColonQualifier) { myColonQualifier = theColonQualifier; } diff --git a/hapi-fhir-client-okhttp/.settings/org.eclipse.jdt.core.prefs b/hapi-fhir-client-okhttp/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..60105c1b951 --- /dev/null +++ b/hapi-fhir-client-okhttp/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/hapi-fhir-jacoco/.settings/org.eclipse.jdt.core.prefs b/hapi-fhir-jacoco/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..60105c1b951 --- /dev/null +++ b/hapi-fhir-jacoco/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/hapi-fhir-jaxrsserver-base/.settings/org.eclipse.jdt.core.prefs b/hapi-fhir-jaxrsserver-base/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..60105c1b951 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/hapi-fhir-jaxrsserver-example/.settings/org.eclipse.jdt.core.prefs b/hapi-fhir-jaxrsserver-example/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..60105c1b951 --- /dev/null +++ b/hapi-fhir-jaxrsserver-example/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu1Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu1Config.java index 695cf2ea97c..08682c4463a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu1Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu1Config.java @@ -10,7 +10,7 @@ package ca.uhn.fhir.jpa.config; * 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 + * 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, @@ -29,7 +29,9 @@ import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Primary; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.ISearchParamRegistry; import ca.uhn.fhir.jpa.dao.SearchParamExtractorDstu1; +import ca.uhn.fhir.jpa.dao.SearchParamRegistryDstu1; import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu1; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.model.api.IResource; @@ -39,9 +41,10 @@ import ca.uhn.fhir.model.dstu2.composite.MetaDt; public class BaseDstu1Config extends BaseConfig { private static FhirContext ourFhirContextDstu1; - @Bean(autowire = Autowire.BY_TYPE) - public IHapiTerminologySvc terminologyService() { - return new HapiTerminologySvcDstu1(); + @Bean + @Primary + public FhirContext defaultFhirContext() { + return fhirContextDstu1(); } @Bean(name = "myFhirContextDstu1") @@ -53,19 +56,22 @@ public class BaseDstu1Config extends BaseConfig { return ourFhirContextDstu1; } - - @Bean - @Primary - public FhirContext defaultFhirContext() { - return fhirContextDstu1(); - } - @Bean(name = "mySystemDaoDstu1", autowire = Autowire.BY_NAME) public ca.uhn.fhir.jpa.dao.IFhirSystemDao, MetaDt> fhirSystemDaoDstu1() { ca.uhn.fhir.jpa.dao.FhirSystemDaoDstu1 retVal = new ca.uhn.fhir.jpa.dao.FhirSystemDaoDstu1(); return retVal; } + @Bean(autowire = Autowire.BY_TYPE) + public SearchParamExtractorDstu1 searchParamExtractor() { + return new SearchParamExtractorDstu1(); + } + + @Bean + public ISearchParamRegistry searchParamRegistry() { + return new SearchParamRegistryDstu1(); + } + @Bean(name = "mySystemProviderDstu1") public ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu1 systemDaoDstu1() { ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu1 retVal = new ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu1(); @@ -73,9 +79,9 @@ public class BaseDstu1Config extends BaseConfig { return retVal; } - @Bean(autowire=Autowire.BY_TYPE) - public SearchParamExtractorDstu1 searchParamExtractor() { - return new SearchParamExtractorDstu1(); + @Bean(autowire = Autowire.BY_TYPE) + public IHapiTerminologySvc terminologyService() { + return new HapiTerminologySvcDstu1(); } - + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java index e0b74a75a68..32a5021cd45 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java @@ -31,7 +31,9 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; +import ca.uhn.fhir.jpa.dao.ISearchParamRegistry; import ca.uhn.fhir.jpa.dao.SearchParamExtractorDstu2; +import ca.uhn.fhir.jpa.dao.SearchParamRegistryDstu2; import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu2; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.model.dstu2.composite.MetaDt; @@ -84,6 +86,11 @@ public class BaseDstu2Config extends BaseConfig { return new SearchParamExtractorDstu2(); } + @Bean + public ISearchParamRegistry searchParamRegistry() { + return new SearchParamRegistryDstu2(); + } + @Bean(name = "mySystemDaoDstu2", autowire = Autowire.BY_NAME) public IFhirSystemDao systemDaoDstu2() { ca.uhn.fhir.jpa.dao.FhirSystemDaoDstu2 retVal = new ca.uhn.fhir.jpa.dao.FhirSystemDaoDstu2(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java index f99f83495d2..802d69e3717 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java @@ -14,7 +14,7 @@ import org.hl7.fhir.dstu3.validation.IResourceValidator.BestPracticeWarningLevel * 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 + * 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, @@ -37,7 +37,9 @@ import ca.uhn.fhir.jpa.config.BaseConfig; import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; +import ca.uhn.fhir.jpa.dao.ISearchParamRegistry; import ca.uhn.fhir.jpa.dao.dstu3.SearchParamExtractorDstu3; +import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3; import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3; import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu3; import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; @@ -50,25 +52,15 @@ import ca.uhn.fhir.validation.IValidatorModule; @EnableTransactionManagement public class BaseDstu3Config extends BaseConfig { - @Bean(autowire = Autowire.BY_TYPE) - public IHapiTerminologySvcDstu3 terminologyService() { - return new HapiTerminologySvcDstu3(); - } - - @Bean(autowire = Autowire.BY_TYPE) - public IHapiTerminologyLoaderSvc terminologyLoaderService() { - return new TerminologyLoaderSvc(); - } - @Bean @Primary public FhirContext fhirContextDstu3() { FhirContext retVal = FhirContext.forDstu3(); - + // Don't strip versions in some places ParserOptions parserOptions = retVal.getParserOptions(); parserOptions.setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.reference"); - + return retVal; } @@ -93,6 +85,16 @@ public class BaseDstu3Config extends BaseConfig { return searchDao; } + @Bean(autowire = Autowire.BY_TYPE) + public SearchParamExtractorDstu3 searchParamExtractor() { + return new SearchParamExtractorDstu3(); + } + + @Bean + public ISearchParamRegistry searchParamRegistry() { + return new SearchParamRegistryDstu3(); + } + @Bean(name = "mySystemDaoDstu3", autowire = Autowire.BY_NAME) public IFhirSystemDao systemDaoDstu3() { ca.uhn.fhir.jpa.dao.dstu3.FhirSystemDaoDstu3 retVal = new ca.uhn.fhir.jpa.dao.dstu3.FhirSystemDaoDstu3(); @@ -107,23 +109,27 @@ public class BaseDstu3Config extends BaseConfig { return retVal; } + @Bean(autowire = Autowire.BY_TYPE) + public IHapiTerminologyLoaderSvc terminologyLoaderService() { + return new TerminologyLoaderSvc(); + } + + @Bean(autowire = Autowire.BY_TYPE) + public IHapiTerminologySvcDstu3 terminologyService() { + return new HapiTerminologySvcDstu3(); + } + + @Bean(autowire = Autowire.BY_TYPE) + public TerminologyUploaderProviderDstu3 terminologyUploaderProvider() { + TerminologyUploaderProviderDstu3 retVal = new TerminologyUploaderProviderDstu3(); + retVal.setContext(fhirContextDstu3()); + return retVal; + } + @Primary @Bean(autowire = Autowire.BY_NAME, name = "myJpaValidationSupportChainDstu3") public IValidationSupport validationSupportChainDstu3() { return new JpaValidationSupportChainDstu3(); } - @Bean(autowire = Autowire.BY_TYPE) - public SearchParamExtractorDstu3 searchParamExtractor() { - return new SearchParamExtractorDstu3(); - } - - @Bean(autowire=Autowire.BY_TYPE) - public TerminologyUploaderProviderDstu3 terminologyUploaderProvider() { - TerminologyUploaderProviderDstu3 retVal = new TerminologyUploaderProviderDstu3(); - retVal.setContext(fhirContextDstu3()); - return retVal; - } - - } 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 8d13f894354..5057c12612f 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 @@ -863,6 +863,9 @@ public abstract class BaseHapiFhirResourceDao extends B return search(map); } + @Autowired + private ISearchParamRegistry mySerarchParamRegistry; + @Override public IBundleProvider search(final SearchParameterMap theParams) { // Notify interceptors @@ -870,7 +873,7 @@ public abstract class BaseHapiFhirResourceDao extends B notifyInterceptors(RestOperationTypeEnum.SEARCH_TYPE, requestDetails); SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this, myResourceIndexedSearchParamUriDao, myForcedIdDao, - myTerminologySvc); + myTerminologySvc, mySerarchParamRegistry); builder.setType(getResourceType(), getResourceName()); return builder.search(theParams); } @@ -899,7 +902,7 @@ public abstract class BaseHapiFhirResourceDao extends B theParams.setPersistResults(false); SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this, myResourceIndexedSearchParamUriDao, myForcedIdDao, - myTerminologySvc); + myTerminologySvc, mySerarchParamRegistry); builder.setType(getResourceType(), getResourceName()); builder.search(theParams); return builder.doGetPids(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamExtractor.java index 8014a78be84..61e825139e8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamExtractor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamExtractor.java @@ -21,9 +21,12 @@ package ca.uhn.fhir.jpa.dao; */ import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.regex.Pattern; +import org.apache.commons.lang3.ObjectUtils; import org.hl7.fhir.instance.model.api.IBaseResource; import org.springframework.beans.factory.annotation.Autowired; @@ -42,12 +45,32 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor @Autowired private FhirContext myContext; + @Autowired + private ISearchParamRegistry mySearchParamRegistry; + public BaseSearchParamExtractor() { super(); } - - public BaseSearchParamExtractor(FhirContext theCtx) { + + public BaseSearchParamExtractor(FhirContext theCtx, ISearchParamRegistry theSearchParamRegistry) { myContext = theCtx; + mySearchParamRegistry = theSearchParamRegistry; + } + + @Override + public List extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) { + List refs = new ArrayList(); + String[] nextPathsSplit = theNextSpDef.getPath().split("\\|"); + for (String nextPath : nextPathsSplit) { + nextPath = nextPath.trim(); + for (Object nextObject : extractValues(nextPath, theResource)) { + if (nextObject == null) { + continue; + } + refs.add(new PathAndRef(nextPath, nextObject)); + } + } + return refs; } protected List extractValues(String thePaths, IBaseResource theResource) { @@ -70,26 +93,18 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor return myContext; } + protected Collection getSearchParams(IBaseResource theResource) { + RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); + Collection retVal = mySearchParamRegistry.getActiveSearchParams(def.getName()).values(); + List defaultList= Collections.emptyList(); + retVal = ObjectUtils.defaultIfNull(retVal, defaultList); + return retVal; + } + @VisibleForTesting void setContextForUnitTest(FhirContext theContext) { myContext = theContext; } - @Override - public List extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) { - List refs = new ArrayList(); - String[] nextPathsSplit = theNextSpDef.getPath().split("\\|"); - for (String nextPath : nextPathsSplit) { - nextPath = nextPath.trim(); - for (Object nextObject : extractValues(nextPath, theResource)) { - if (nextObject == null) { - continue; - } - refs.add(new PathAndRef(nextPath, nextObject)); - } - } - return refs; - } - } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamRegistry.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamRegistry.java new file mode 100644 index 00000000000..721b129e596 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamRegistry.java @@ -0,0 +1,65 @@ +package ca.uhn.fhir.jpa.dao; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.PostConstruct; + +import org.apache.commons.lang3.Validate; +import org.springframework.beans.factory.annotation.Autowired; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.context.RuntimeSearchParam; + +public abstract class BaseSearchParamRegistry implements ISearchParamRegistry { + + private Map> myBuiltInSearchParams; + + @Autowired + private FhirContext myCtx; + + @Autowired + private Collection> myDaos; + + public BaseSearchParamRegistry() { + super(); + } + + @Override + public void forceRefresh() { + // nothing by default + } + + public Map> getBuiltInSearchParams() { + return myBuiltInSearchParams; + } + + @Override + public Map getActiveSearchParams(String theResourceName) { + Validate.notBlank(theResourceName, "theResourceName must not be blank or null"); + + return myBuiltInSearchParams.get(theResourceName); + } + + @PostConstruct + public void postConstruct() { + Map> resourceNameToSearchParams = new HashMap>(); + + for (IFhirResourceDao nextDao : myDaos) { + RuntimeResourceDefinition nextResDef = myCtx.getResourceDefinition(nextDao.getResourceType()); + String nextResourceName = nextResDef.getName(); + HashMap nameToParam = new HashMap(); + resourceNameToSearchParams.put(nextResourceName, nameToParam); + + for (RuntimeSearchParam nextSp : nextResDef.getSearchParams()) { + nameToParam.put(nextSp.getName(), nextSp); + } + } + + myBuiltInSearchParams = Collections.unmodifiableMap(resourceNameToSearchParams); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java index c84e72d1bc3..52805df00a1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java @@ -16,7 +16,7 @@ import org.apache.commons.lang3.time.DateUtils; * 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 + * 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, @@ -35,26 +35,28 @@ public class DaoConfig { // *** // update setter javadoc if default changes // *** - private boolean myAllowExternalReferences = false; + private boolean myAllowExternalReferences = false; // *** // update setter javadoc if default changes // *** - private boolean myAllowInlineMatchUrlReferences = false; + private boolean myAllowInlineMatchUrlReferences = false; private boolean myAllowMultipleDelete; + private boolean myDefaultSearchParamsCanBeOverridden = false; // *** // update setter javadoc if default changes // *** private int myDeferIndexingForCodesystemsOfSize = 2000; private boolean myDeleteStaleSearches = true; + // *** // update setter javadoc if default changes // *** private long myExpireSearchResultsAfterMillis = DateUtils.MILLIS_PER_HOUR; - + private int myHardTagListLimit = 1000; - + private int myIncludeLimit = 2000; // *** @@ -63,19 +65,19 @@ public class DaoConfig { private boolean myIndexContainedResources = true; private List myInterceptors; - // *** // update setter javadoc if default changes // *** private int myMaximumExpansionSize = 5000; + private ResourceEncodingEnum myResourceEncoding = ResourceEncodingEnum.JSONC; private boolean mySchedulingDisabled; private boolean mySubscriptionEnabled; - + private long mySubscriptionPollDelay = 1000; - + private Long mySubscriptionPurgeInactiveAfterMillis; private Set myTreatBaseUrlsAsLocal = new HashSet(); @@ -91,42 +93,44 @@ public class DaoConfig { public int getDeferIndexingForCodesystemsOfSize() { return myDeferIndexingForCodesystemsOfSize; } + /** - * Sets the number of milliseconds that search results for a given client search + * Sets the number of milliseconds that search results for a given client search * should be preserved before being purged from the database. *

- * Search results are stored in the database so that they can be paged over multiple + * Search results are stored in the database so that they can be paged over multiple * requests. After this * number of milliseconds, they will be deleted from the database, and any paging links - * (next/prev links in search response bundles) will become invalid. Defaults to 1 hour. + * (next/prev links in search response bundles) will become invalid. Defaults to 1 hour. *

*

+ * * @see To disable this feature entirely, see {@link #setExpireSearchResults(boolean)} - *

+ *

* * @since 1.5 */ public long getExpireSearchResultsAfterMillis() { return myExpireSearchResultsAfterMillis; } - + /** * Gets the maximum number of results to return in a GetTags query (DSTU1 only) */ public int getHardTagListLimit() { return myHardTagListLimit; } - + public int getIncludeLimit() { return myIncludeLimit; } - + /** * Returns the interceptors which will be notified of operations. * * @see #setInterceptors(List) * @deprecated As of 2.2 this method is deprecated. There is no good reason to register an interceptor - * with the DaoConfig and not with the server via {@link RestfulServer#registerInterceptor(IServerInterceptor)} + * with the DaoConfig and not with the server via {@link RestfulServer#registerInterceptor(IServerInterceptor)} */ @Deprecated public List getInterceptors() { @@ -135,22 +139,26 @@ public class DaoConfig { } return myInterceptors; } - + /** * See {@link #setMaximumExpansionSize(int)} */ public int getMaximumExpansionSize() { return myMaximumExpansionSize; } + public ResourceEncodingEnum getResourceEncoding() { return myResourceEncoding; } + public long getSubscriptionPollDelay() { return mySubscriptionPollDelay; } + public Long getSubscriptionPurgeInactiveAfterMillis() { return mySubscriptionPurgeInactiveAfterMillis; } + /** * This setting may be used to advise the server that any references found in * resources that have any of the base URLs given here will be replaced with @@ -165,6 +173,7 @@ public class DaoConfig { public Set getTreatBaseUrlsAsLocal() { return myTreatBaseUrlsAsLocal; } + /** * If set to true (default is false) the server will allow * resources to have references to external servers. For example if this server is @@ -179,7 +188,7 @@ public class DaoConfig { *

* Note that external references will be indexed by the server and may be searched * (e.g. Patient:organization), but - * chained searches (e.g. Patient:organization.name) will not work across + * chained searches (e.g. Patient:organization.name) will not work across * these references. *

*

@@ -193,19 +202,37 @@ public class DaoConfig { public boolean isAllowExternalReferences() { return myAllowExternalReferences; } + /** * @see #setAllowInlineMatchUrlReferences(boolean) */ public boolean isAllowInlineMatchUrlReferences() { return myAllowInlineMatchUrlReferences; } + public boolean isAllowMultipleDelete() { return myAllowMultipleDelete; } /** - * If this is set to false (default is true) the stale search deletion - * task will be disabled (meaning that search results will be retained in the database indefinitely). USE WITH CAUTION. + * If set to {@code true} the default search params (i.e. the search parameters that are + * defined by the FHIR specification itself) may be overridden by uploading search + * parameters to the server with the same code as the built-in search parameter. + *

+ * This can be useful if you want to be able to disable or alter + * the behaviour of the default search parameters. + *

+ *

+ * The default value for this setting is {@code false} + *

+ */ + public boolean isDefaultSearchParamsCanBeOverridden() { + return myDefaultSearchParamsCanBeOverridden; + } + + /** + * If this is set to false (default is true) the stale search deletion + * task will be disabled (meaning that search results will be retained in the database indefinitely). USE WITH CAUTION. *

* This feature is useful if you want to define your own process for deleting these (e.g. because * you are running in a cluster) @@ -217,7 +244,7 @@ public class DaoConfig { /** * Should contained IDs be indexed the same way that non-contained IDs are (default is - * true) + * true) */ public boolean isIndexContainedResources() { return myIndexContainedResources; @@ -248,7 +275,7 @@ public class DaoConfig { *

* Note that external references will be indexed by the server and may be searched * (e.g. Patient:organization), but - * chained searches (e.g. Patient:organization.name) will not work across + * chained searches (e.g. Patient:organization.name) will not work across * these references. *

*

@@ -271,6 +298,7 @@ public class DaoConfig { *

* Default is false for now, as this is an experimental feature. *

+ * * @since 1.5 */ public void setAllowInlineMatchUrlReferences(boolean theAllowInlineMatchUrlReferences) { @@ -281,6 +309,22 @@ public class DaoConfig { myAllowMultipleDelete = theAllowMultipleDelete; } + /** + * If set to {@code true} the default search params (i.e. the search parameters that are + * defined by the FHIR specification itself) may be overridden by uploading search + * parameters to the server with the same code as the built-in search parameter. + *

+ * This can be useful if you want to be able to disable or alter + * the behaviour of the default search parameters. + *

+ *

+ * The default value for this setting is {@code false} + *

+ */ + public void setDefaultSearchParamsCanBeOverridden(boolean theDefaultSearchParamsCanBeOverridden) { + myDefaultSearchParamsCanBeOverridden = theDefaultSearchParamsCanBeOverridden; + } + /** * When a code system is added that contains more than this number of codes, * the code system will be indexed later in an incremental process in order to @@ -294,8 +338,8 @@ public class DaoConfig { } /** - * If this is set to false (default is true) the stale search deletion - * task will be disabled (meaning that search results will be retained in the database indefinitely). USE WITH CAUTION. + * If this is set to false (default is true) the stale search deletion + * task will be disabled (meaning that search results will be retained in the database indefinitely). USE WITH CAUTION. *

* This feature is useful if you want to define your own process for deleting these (e.g. because * you are running in a cluster) @@ -306,17 +350,18 @@ public class DaoConfig { } /** - * Sets the number of milliseconds that search results for a given client search + * Sets the number of milliseconds that search results for a given client search * should be preserved before being purged from the database. *

- * Search results are stored in the database so that they can be paged over multiple + * Search results are stored in the database so that they can be paged over multiple * requests. After this * number of milliseconds, they will be deleted from the database, and any paging links - * (next/prev links in search response bundles) will become invalid. Defaults to 1 hour. + * (next/prev links in search response bundles) will become invalid. Defaults to 1 hour. *

*

+ * * @see To disable this feature entirely, see {@link #setExpireSearchResults(boolean)} - *

+ *

* @since 1.5 */ public void setExpireSearchResultsAfterMillis(long theExpireSearchResultsAfterMillis) { @@ -329,7 +374,7 @@ public class DaoConfig { * paging provider instead. * * @deprecated This method does not do anything. Configure the page size on your - * paging provider instead. Deprecated in HAPI FHIR 2.3 (Jan 2017) + * paging provider instead. Deprecated in HAPI FHIR 2.3 (Jan 2017) */ @Deprecated public void setHardSearchLimit(@SuppressWarnings("unused") int theHardSearchLimit) { @@ -354,7 +399,7 @@ public class DaoConfig { /** * Should contained IDs be indexed the same way that non-contained IDs are (default is - * true) + * true) */ public void setIndexContainedResources(boolean theIndexContainedResources) { myIndexContainedResources = theIndexContainedResources; @@ -362,8 +407,9 @@ public class DaoConfig { /** * This may be used to optionally register server interceptors directly against the DAOs. + * * @deprecated As of 2.2 this method is deprecated. There is no good reason to register an interceptor - * with the DaoConfig and not with the server via {@link RestfulServer#registerInterceptor(IServerInterceptor)} + * with the DaoConfig and not with the server via {@link RestfulServer#registerInterceptor(IServerInterceptor)} */ @Deprecated public void setInterceptors(IServerInterceptor... theInterceptor) { @@ -375,8 +421,9 @@ public class DaoConfig { /** * This may be used to optionally register server interceptors directly against the DAOs. + * * @deprecated As of 2.2 this method is deprecated. There is no good reason to register an interceptor - * with the DaoConfig and not with the server via {@link RestfulServer#registerInterceptor(IServerInterceptor)} + * with the DaoConfig and not with the server via {@link RestfulServer#registerInterceptor(IServerInterceptor)} */ @Deprecated public void setInterceptors(List theInterceptors) { @@ -419,11 +466,11 @@ public class DaoConfig { } mySubscriptionPurgeInactiveAfterMillis = theMillis; } - + public void setSubscriptionPurgeInactiveAfterSeconds(int theSeconds) { setSubscriptionPurgeInactiveAfterMillis(theSeconds * DateUtils.MILLIS_PER_SECOND); } - + /** * This setting may be used to advise the server that any references found in * resources that have any of the base URLs given here will be replaced with @@ -435,8 +482,9 @@ public class DaoConfig { * convert this reference to Patient/1 *

* - * @param theTreatBaseUrlsAsLocal The set of base URLs. May be null, which - * means no references will be treated as external + * @param theTreatBaseUrlsAsLocal + * The set of base URLs. May be null, which + * means no references will be treated as external */ public void setTreatBaseUrlsAsLocal(Set theTreatBaseUrlsAsLocal) { HashSet treatBaseUrlsAsLocal = new HashSet(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoPatientDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoPatientDstu2.java index 76e43ae12d7..43eb3ec6556 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoPatientDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoPatientDstu2.java @@ -26,6 +26,7 @@ import javax.servlet.http.HttpServletRequest; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.springframework.beans.factory.annotation.Autowired; import ca.uhn.fhir.jpa.dao.SearchParameterMap.EverythingModeEnum; import ca.uhn.fhir.model.api.IResource; @@ -46,6 +47,9 @@ public class FhirResourceDaoPatientDstu2 extends FhirResourceDaoDstu2im super(); } + @Autowired + private ISearchParamRegistry mySerarchParamRegistry; + private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative) { SearchParameterMap paramMap = new SearchParameterMap(); if (theCount != null) { @@ -65,7 +69,7 @@ public class FhirResourceDaoPatientDstu2 extends FhirResourceDaoDstu2im paramMap.add("_id", new StringParam(theId.getIdPart())); } - SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this, myResourceIndexedSearchParamUriDao, myForcedIdDao, myTerminologySvc); + SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this, myResourceIndexedSearchParamUriDao, myForcedIdDao, myTerminologySvc, mySerarchParamRegistry); builder.setType(getResourceType(), getResourceName()); return builder.search(paramMap); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchParamRegistry.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchParamRegistry.java new file mode 100644 index 00000000000..540c7b60275 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchParamRegistry.java @@ -0,0 +1,13 @@ +package ca.uhn.fhir.jpa.dao; + +import java.util.Map; + +import ca.uhn.fhir.context.RuntimeSearchParam; + +public interface ISearchParamRegistry { + + Map getActiveSearchParams(String theResourceName); + + void forceRefresh(); + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java index dfda033113c..a43e31f50c8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java @@ -41,6 +41,7 @@ import org.apache.commons.lang3.tuple.Pair; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; @@ -95,10 +96,12 @@ public class SearchBuilder { private IFulltextSearchSvc mySearchDao; private Search mySearchEntity; private ISearchResultDao mySearchResultDao; + private ISearchParamRegistry mySerarchParamRegistry; + private IHapiTerminologySvc myTerminologySvc; public SearchBuilder(FhirContext theFhirContext, EntityManager theEntityManager, PlatformTransactionManager thePlatformTransactionManager, IFulltextSearchSvc theSearchDao, ISearchResultDao theSearchResultDao, BaseHapiFhirDao theDao, - IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao, IForcedIdDao theForcedIdDao, IHapiTerminologySvc theTerminologySvc) { + IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao, IForcedIdDao theForcedIdDao, IHapiTerminologySvc theTerminologySvc, ISearchParamRegistry theSearchParamRegistry) { myContext = theFhirContext; myEntityManager = theEntityManager; myPlatformTransactionManager = thePlatformTransactionManager; @@ -108,6 +111,7 @@ public class SearchBuilder { myResourceIndexedSearchParamUriDao = theResourceIndexedSearchParamUriDao; myForcedIdDao = theForcedIdDao; myTerminologySvc = theTerminologySvc; + mySerarchParamRegistry = theSearchParamRegistry; } private void addPredicateComposite(RuntimeSearchParam theParamDef, List theNextAnd) { @@ -1380,45 +1384,10 @@ public class SearchBuilder { return singleCode; } - private String determineSystemIfMissing(String theParamName, String code, String system) { - if (system == null) { - RuntimeSearchParam param = getSearchParam(theParamName); - if (param != null) { - Set valueSetUris = Sets.newHashSet(); - for (String nextPath : param.getPathsSplit()) { - BaseRuntimeChildDefinition def = myContext.newTerser().getDefinition(myResourceType, nextPath); - if (def instanceof BaseRuntimeDeclaredChildDefinition) { - String valueSet = ((BaseRuntimeDeclaredChildDefinition) def).getBindingValueSet(); - if (isNotBlank(valueSet)) { - valueSetUris.add(valueSet); - } - } - } - if (valueSetUris.size() == 1) { - List candidateCodes = myTerminologySvc.expandValueSet(valueSetUris.iterator().next()); - for (VersionIndependentConcept nextCandidate : candidateCodes) { - if (nextCandidate.getCode().equals(code)) { - system = nextCandidate.getSystem(); - break; - } - } - } - } - } - return system; - } - private Predicate createResourceLinkPathPredicate(String theParamName, Root from) { return createResourceLinkPathPredicate(myContext, theParamName, from, myResourceType); } - private static Predicate createResourceLinkPathPredicate(FhirContext theContext, String theParamName, Root from, Class resourceType) { - RuntimeSearchParam param = theContext.getResourceDefinition(resourceType).getSearchParam(theParamName); - List path = param.getPathsSplit(); - Predicate type = from.get("mySourcePath").in(path); - return type; - } - private TypedQuery createSearchAllByTypeQuery(DateRangeParam theLastUpdated) { CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery cq = builder.createQuery(Long.class); @@ -1532,10 +1501,32 @@ public class SearchBuilder { createSort(theBuilder, theFrom, theSort.getChain(), theOrders, thePredicates); } - private RuntimeSearchParam getSearchParam(String theParamName) { - RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceType); - RuntimeSearchParam param = resourceDef.getSearchParam(theParamName); - return param; + private String determineSystemIfMissing(String theParamName, String code, String system) { + if (system == null) { + RuntimeSearchParam param = getSearchParam(theParamName); + if (param != null) { + Set valueSetUris = Sets.newHashSet(); + for (String nextPath : param.getPathsSplit()) { + BaseRuntimeChildDefinition def = myContext.newTerser().getDefinition(myResourceType, nextPath); + if (def instanceof BaseRuntimeDeclaredChildDefinition) { + String valueSet = ((BaseRuntimeDeclaredChildDefinition) def).getBindingValueSet(); + if (isNotBlank(valueSet)) { + valueSetUris.add(valueSet); + } + } + } + if (valueSetUris.size() == 1) { + List candidateCodes = myTerminologySvc.expandValueSet(valueSetUris.iterator().next()); + for (VersionIndependentConcept nextCandidate : candidateCodes) { + if (nextCandidate.getCode().equals(code)) { + system = nextCandidate.getSystem(); + break; + } + } + } + } + } + return system; } public Set doGetPids() { @@ -1621,6 +1612,12 @@ public class SearchBuilder { doSetPids(resultList); } + private RuntimeSearchParam getSearchParam(String theParamName) { + RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceType); + RuntimeSearchParam param = resourceDef.getSearchParam(theParamName); + return param; + } + private void loadResourcesByPid(Collection theIncludePids, List theResourceListToPopulate, Set theRevIncludedPids, boolean theForHistoryOperation) { EntityManager entityManager = myEntityManager; FhirContext context = myContext; @@ -1820,7 +1817,8 @@ public class SearchBuilder { doInitializeSearch(); - RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceType); +// RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceType); + Map searchParams = mySerarchParamRegistry.getActiveSearchParams(myResourceName); for (Entry>> nextParamEntry : params.entrySet()) { String nextParamName = nextParamEntry.getKey(); @@ -1875,7 +1873,7 @@ public class SearchBuilder { } else { - RuntimeSearchParam nextParamDef = resourceDef.getSearchParam(nextParamName); + RuntimeSearchParam nextParamDef = searchParams.get(nextParamName); if (nextParamDef != null) { switch (nextParamDef.getParamType()) { case DATE: @@ -1953,7 +1951,7 @@ public class SearchBuilder { } } - + public void setType(Class theResourceType, String theResourceName) { myResourceType = theResourceType; myResourceName = theResourceName; @@ -2044,6 +2042,13 @@ public class SearchBuilder { return likeExpression.replace("%", "[%]") + "%"; } + private static Predicate createResourceLinkPathPredicate(FhirContext theContext, String theParamName, Root from, Class resourceType) { + RuntimeSearchParam param = theContext.getResourceDefinition(resourceType).getSearchParam(theParamName); + List path = param.getPathsSplit(); + Predicate type = from.get("mySourcePath").in(path); + return type; + } + private static List filterResourceIdsByLastUpdated(EntityManager theEntityManager, final DateRangeParam theLastUpdated, Collection thePids) { CriteriaBuilder builder = theEntityManager.getCriteriaBuilder(); CriteriaQuery cq = builder.createQuery(Long.class); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu1.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu1.java index 268aab2eca5..a40804e04b2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu1.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu1.java @@ -25,6 +25,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -38,8 +39,6 @@ import org.apache.commons.lang3.tuple.Pair; import org.hl7.fhir.instance.model.api.IBaseResource; import ca.uhn.fhir.context.ConfigurationException; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords; @@ -79,8 +78,8 @@ public class SearchParamExtractorDstu1 extends BaseSearchParamExtractor implemen public Set extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) { HashSet retVal = new HashSet(); - RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); - for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { + Collection searchParams = getSearchParams(theResource); + for (RuntimeSearchParam nextSpDef : searchParams) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.DATE) { continue; } @@ -130,12 +129,13 @@ public class SearchParamExtractorDstu1 extends BaseSearchParamExtractor implemen return retVal; } + @Override public HashSet extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) { HashSet retVal = new HashSet(); - RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); - for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { + Collection searchParams = getSearchParams(theResource); + for (RuntimeSearchParam nextSpDef : searchParams) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.NUMBER) { continue; } @@ -229,8 +229,8 @@ public class SearchParamExtractorDstu1 extends BaseSearchParamExtractor implemen public Set extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) { HashSet retVal = new HashSet(); - RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); - for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { + Collection searchParams = getSearchParams(theResource); + for (RuntimeSearchParam nextSpDef : searchParams) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.QUANTITY) { continue; } @@ -277,8 +277,8 @@ public class SearchParamExtractorDstu1 extends BaseSearchParamExtractor implemen public Set extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) { HashSet retVal = new HashSet(); - RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); - for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { + Collection searchParams = getSearchParams(theResource); + for (RuntimeSearchParam nextSpDef : searchParams) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.STRING) { continue; } @@ -363,8 +363,8 @@ public class SearchParamExtractorDstu1 extends BaseSearchParamExtractor implemen public Set extractSearchParamTokens(ResourceTable theEntity, IBaseResource theResource) { HashSet retVal = new HashSet(); - RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); - for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { + Collection searchParams = getSearchParams(theResource); + for (RuntimeSearchParam nextSpDef : searchParams) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.TOKEN) { continue; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu2.java index 76ff961b072..eb0f6be67da 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu2.java @@ -24,6 +24,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -120,8 +121,8 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen public Set extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) { HashSet retVal = new HashSet(); - RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); - for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { + Collection searchParams = getSearchParams(theResource); + for (RuntimeSearchParam nextSpDef : searchParams) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.DATE) { continue; } @@ -181,8 +182,8 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen public HashSet extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) { HashSet retVal = new HashSet(); - RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); - for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { + Collection searchParams = getSearchParams(theResource); + for (RuntimeSearchParam nextSpDef : searchParams) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.NUMBER) { continue; } @@ -281,8 +282,8 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen public Set extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) { HashSet retVal = new HashSet(); - RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); - for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { + Collection searchParams = getSearchParams(theResource); + for (RuntimeSearchParam nextSpDef : searchParams) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.QUANTITY) { continue; } @@ -335,23 +336,25 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen public Set extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) { HashSet retVal = new HashSet(); - RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); - for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { + String resourceName = getContext().getResourceDefinition(theResource).getName(); + + Collection searchParams = getSearchParams(theResource); + for (RuntimeSearchParam nextSpDef : searchParams) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.STRING) { continue; } String nextPath = nextSpDef.getPath(); - String resourceName = nextSpDef.getName(); + String nextSpName = nextSpDef.getName(); if (isBlank(nextPath)) { // TODO: implement phonetic, and any others that have no path - if ("Questionnaire".equals(def.getName()) && nextSpDef.getName().equals("title")) { + if ("Questionnaire".equals(resourceName) && nextSpDef.getName().equals("title")) { Questionnaire q = (Questionnaire) theResource; String title = q.getGroup().getTitle(); - addSearchTerm(theEntity, retVal, resourceName, title); + addSearchTerm(theEntity, retVal, nextSpName, title); } continue; } @@ -369,7 +372,7 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen if (nextObject instanceof IPrimitiveDatatype) { IPrimitiveDatatype nextValue = (IPrimitiveDatatype) nextObject; String searchTerm = nextValue.getValueAsString(); - addSearchTerm(theEntity, retVal, resourceName, searchTerm); + addSearchTerm(theEntity, retVal, nextSpName, searchTerm); } else { if (nextObject instanceof BaseHumanNameDt) { ArrayList allNames = new ArrayList(); @@ -377,7 +380,7 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen allNames.addAll(nextHumanName.getFamily()); allNames.addAll(nextHumanName.getGiven()); for (StringDt nextName : allNames) { - addSearchTerm(theEntity, retVal, resourceName, nextName.getValue()); + addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue()); } } else if (nextObject instanceof AddressDt) { ArrayList allNames = new ArrayList(); @@ -388,16 +391,16 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen allNames.add(nextAddress.getCountryElement()); allNames.add(nextAddress.getPostalCodeElement()); for (StringDt nextName : allNames) { - addSearchTerm(theEntity, retVal, resourceName, nextName.getValue()); + addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue()); } } else if (nextObject instanceof ContactPointDt) { ContactPointDt nextContact = (ContactPointDt) nextObject; if (nextContact.getValueElement().isEmpty() == false) { - addSearchTerm(theEntity, retVal, resourceName, nextContact.getValue()); + addSearchTerm(theEntity, retVal, nextSpName, nextContact.getValue()); } } else { if (!multiType) { - throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass()); + throw new ConfigurationException("Search param " + nextSpName + " is of unexpected datatype: " + nextObject.getClass()); } } } @@ -423,8 +426,8 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen useSystem = vs.getCodeSystem().getSystem(); } - RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); - for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { + Collection searchParams = getSearchParams(theResource); + for (RuntimeSearchParam nextSpDef : searchParams) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.TOKEN) { continue; } @@ -575,8 +578,8 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen public Set extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) { HashSet retVal = new HashSet(); - RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); - for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { + Collection searchParams = getSearchParams(theResource); + for (RuntimeSearchParam nextSpDef : searchParams) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.URI) { continue; } @@ -622,6 +625,7 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen return retVal; } + private void extractTokensFromCodeableConcept(List theSystems, List theCodes, CodeableConceptDt theCodeableConcept, ResourceTable theEntity, Set theListToPopulate, RuntimeSearchParam theParameterDef) { for (CodingDt nextCoding : theCodeableConcept.getCoding()) { extractTokensFromCoding(theSystems, theCodes, theEntity, theListToPopulate, theParameterDef, nextCoding); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamRegistryDstu1.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamRegistryDstu1.java new file mode 100644 index 00000000000..5f04468482d --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamRegistryDstu1.java @@ -0,0 +1,6 @@ +package ca.uhn.fhir.jpa.dao; + +public class SearchParamRegistryDstu1 extends BaseSearchParamRegistry { + // nothing yet + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamRegistryDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamRegistryDstu2.java new file mode 100644 index 00000000000..b7fd4baecf3 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamRegistryDstu2.java @@ -0,0 +1,6 @@ +package ca.uhn.fhir.jpa.dao; + +public class SearchParamRegistryDstu2 extends BaseSearchParamRegistry { + // nothing yet + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoPatientDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoPatientDstu3.java index 2ac1e302de2..7b93b004b87 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoPatientDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoPatientDstu3.java @@ -27,8 +27,10 @@ import javax.servlet.http.HttpServletRequest; import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.springframework.beans.factory.annotation.Autowired; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient; +import ca.uhn.fhir.jpa.dao.ISearchParamRegistry; import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.dao.SearchParameterMap; import ca.uhn.fhir.jpa.dao.SearchParameterMap.EverythingModeEnum; @@ -45,6 +47,9 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetai public class FhirResourceDaoPatientDstu3 extends FhirResourceDaoDstu3implements IFhirResourceDaoPatient { + @Autowired + private ISearchParamRegistry mySerarchParamRegistry; + private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative) { SearchParameterMap paramMap = new SearchParameterMap(); if (theCount != null) { @@ -64,7 +69,7 @@ public class FhirResourceDaoPatientDstu3 extends FhirResourceDaoDstu3im paramMap.add("_id", new StringParam(theId.getIdPart())); } - SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this, myResourceIndexedSearchParamUriDao, myForcedIdDao, myTerminologySvc); + SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this, myResourceIndexedSearchParamUriDao, myForcedIdDao, myTerminologySvc, mySerarchParamRegistry); builder.setType(getResourceType(), getResourceName()); return builder.search(paramMap); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/SearchParamExtractorDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/SearchParamExtractorDstu3.java index ba4b4c43a04..ed6612fc852 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/SearchParamExtractorDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/SearchParamExtractorDstu3.java @@ -24,6 +24,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; @@ -56,7 +57,6 @@ import org.hl7.fhir.dstu3.model.Location.LocationPositionComponent; import org.hl7.fhir.dstu3.model.Patient.PatientCommunicationComponent; import org.hl7.fhir.dstu3.model.Period; import org.hl7.fhir.dstu3.model.Quantity; -import org.hl7.fhir.dstu3.model.Questionnaire; import org.hl7.fhir.dstu3.model.Range; import org.hl7.fhir.dstu3.model.SimpleQuantity; import org.hl7.fhir.dstu3.model.StringType; @@ -73,11 +73,11 @@ import com.google.common.annotations.VisibleForTesting; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.BaseSearchParamExtractor; import ca.uhn.fhir.jpa.dao.ISearchParamExtractor; +import ca.uhn.fhir.jpa.dao.ISearchParamRegistry; import ca.uhn.fhir.jpa.dao.PathAndRef; import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords; @@ -97,7 +97,7 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen @Autowired private org.hl7.fhir.dstu3.hapi.validation.IValidationSupport myValidationSupport; - + /** * Constructor */ @@ -105,8 +105,8 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen super(); } - public SearchParamExtractorDstu3(FhirContext theCtx, IValidationSupport theValidationSupport) { - super(theCtx); + public SearchParamExtractorDstu3(FhirContext theCtx, IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry) { + super(theCtx, theSearchParamRegistry); myValidationSupport = theValidationSupport; } @@ -147,8 +147,8 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen public Set extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) { HashSet retVal = new HashSet(); - RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); - for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { + Collection searchParams = getSearchParams(theResource); + for (RuntimeSearchParam nextSpDef : searchParams) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.DATE) { continue; } @@ -226,8 +226,8 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen public HashSet extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) { HashSet retVal = new HashSet(); - RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); - for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { + Collection searchParams = getSearchParams(theResource); + for (RuntimeSearchParam nextSpDef : searchParams) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.NUMBER) { continue; } @@ -320,8 +320,8 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen public Set extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) { HashSet retVal = new HashSet(); - RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); - for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { + Collection searchParams = getSearchParams(theResource); + for (RuntimeSearchParam nextSpDef : searchParams) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.QUANTITY) { continue; } @@ -384,24 +384,28 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen public Set extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) { HashSet retVal = new HashSet(); - RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); - for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { + String resourceName = getContext().getResourceDefinition(theResource).getName(); + + Collection searchParams = getSearchParams(theResource); + for (RuntimeSearchParam nextSpDef : searchParams) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.STRING) { continue; } String nextPath = nextSpDef.getPath(); - String resourceName = nextSpDef.getName(); + String nextSpName = nextSpDef.getName(); if (isBlank(nextPath)) { - // TODO: implement phonetic, and any others that have no path - - if ("Questionnaire".equals(def.getName()) && nextSpDef.getName().equals("title")) { - Questionnaire q = (Questionnaire) theResource; - String title = "";// q.getGroup().getTitle(); - addSearchTerm(theEntity, retVal, resourceName, title); - } +// // TODO: implement phonetic, and any others that have no path +// +// // TODO: do we still need this check? +// if ("Questionnaire".equals(nextSpName) && nextSpDef.getName().equals("title")) { +// Questionnaire q = (Questionnaire) theResource; +// String title = "";// q.getGroup().getTitle(); +// addSearchTerm(theEntity, retVal, nextSpName, title); +// } + continue; } @@ -418,7 +422,7 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen if (nextObject instanceof IPrimitiveType) { IPrimitiveType nextValue = (IPrimitiveType) nextObject; String searchTerm = nextValue.getValueAsString(); - addSearchTerm(theEntity, retVal, resourceName, searchTerm); + addSearchTerm(theEntity, retVal, nextSpName, searchTerm); } else { if (nextObject instanceof HumanName) { ArrayList allNames = new ArrayList(); @@ -428,7 +432,7 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen } allNames.addAll(nextHumanName.getGiven()); for (StringType nextName : allNames) { - addSearchTerm(theEntity, retVal, resourceName, nextName.getValue()); + addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue()); } } else if (nextObject instanceof Address) { ArrayList allNames = new ArrayList(); @@ -439,29 +443,29 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen allNames.add(nextAddress.getCountryElement()); allNames.add(nextAddress.getPostalCodeElement()); for (StringType nextName : allNames) { - addSearchTerm(theEntity, retVal, resourceName, nextName.getValue()); + addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue()); } } else if (nextObject instanceof ContactPoint) { ContactPoint nextContact = (ContactPoint) nextObject; if (nextContact.getValueElement().isEmpty() == false) { - addSearchTerm(theEntity, retVal, resourceName, nextContact.getValue()); + addSearchTerm(theEntity, retVal, nextSpName, nextContact.getValue()); } } else if (nextObject instanceof Quantity) { BigDecimal value = ((Quantity) nextObject).getValue(); if (value != null) { - addSearchTerm(theEntity, retVal, resourceName, value.toPlainString()); + addSearchTerm(theEntity, retVal, nextSpName, value.toPlainString()); } } else if (nextObject instanceof Range) { SimpleQuantity low = ((Range) nextObject).getLow(); if (low != null) { BigDecimal value = low.getValue(); if (value != null) { - addSearchTerm(theEntity, retVal, resourceName, value.toPlainString()); + addSearchTerm(theEntity, retVal, nextSpName, value.toPlainString()); } } } else { if (!multiType) { - throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass()); + throw new ConfigurationException("Search param " + nextSpName + " is of unexpected datatype: " + nextObject.getClass()); } } } @@ -486,8 +490,8 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen useSystem = cs.getUrl(); } - RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); - for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { + Collection searchParams = getSearchParams(theResource); + for (RuntimeSearchParam nextSpDef : searchParams) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.TOKEN) { continue; } @@ -633,8 +637,8 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen public Set extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) { HashSet retVal = new HashSet(); - RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); - for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { + Collection searchParams = getSearchParams(theResource); + for (RuntimeSearchParam nextSpDef : searchParams) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.URI) { continue; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/SearchParamRegistryDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/SearchParamRegistryDstu3.java new file mode 100644 index 00000000000..75123b7fa8b --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/SearchParamRegistryDstu3.java @@ -0,0 +1,194 @@ +package ca.uhn.fhir.jpa.dao.dstu3; + +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.apache.commons.lang3.time.DateUtils; +import org.hl7.fhir.dstu3.model.CodeType; +import org.hl7.fhir.dstu3.model.SearchParameter; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.springframework.beans.factory.annotation.Autowired; + +import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.context.RuntimeSearchParam.RuntimeSearchParamStatusEnum; +import ca.uhn.fhir.jpa.dao.BaseSearchParamRegistry; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.jpa.util.StopWatch; +import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum; +import ca.uhn.fhir.rest.server.IBundleProvider; + +public class SearchParamRegistryDstu3 extends BaseSearchParamRegistry { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParamRegistryDstu3.class); + + @Autowired + private IFhirResourceDao mySpDao; + + private long myLastRefresh; + + private volatile Map> myActiveSearchParams; + + @Autowired + private DaoConfig myDaoConfig; + + @Override + public Map getActiveSearchParams(String theResourceName) { + + long refreshInterval = 60 * DateUtils.MILLIS_PER_MINUTE; + if (System.currentTimeMillis() - refreshInterval > myLastRefresh) { + StopWatch sw = new StopWatch(); + + Map> searchParams = new HashMap>(); + for (Entry> nextBuiltInEntry : getBuiltInSearchParams().entrySet()) { + for (RuntimeSearchParam nextParam : nextBuiltInEntry.getValue().values()) { + String nextResourceName = nextBuiltInEntry.getKey(); + getSearchParamMap(searchParams, nextResourceName).put(nextParam.getName(), nextParam); + } + } + + IBundleProvider allSearchParamsBp = mySpDao.search(new SearchParameterMap()); + int size = allSearchParamsBp.size(); + + // Just in case.. + if (size > 10000) { + ourLog.warn("Unable to support >10000 search params!"); + size = 10000; + } + + List allSearchParams = allSearchParamsBp.getResources(0, size); + for (IBaseResource nextResource : allSearchParams) { + SearchParameter nextSp = (SearchParameter) nextResource; + RuntimeSearchParam runtimeSp = toRuntimeSp(nextSp); + if (runtimeSp == null) { + continue; + } + + int dotIdx = runtimeSp.getPath().indexOf('.'); + if (dotIdx == -1) { + ourLog.warn("Can not determine resource type of {}", runtimeSp.getPath()); + continue; + } + String resourceType = runtimeSp.getPath().substring(0, dotIdx); + + Map searchParamMap = getSearchParamMap(searchParams, resourceType); + String name = runtimeSp.getName(); + if (myDaoConfig.isDefaultSearchParamsCanBeOverridden() || !searchParamMap.containsKey(name)) { + searchParamMap.put(name, runtimeSp); + } + } + + Map> activeSearchParams = new HashMap>(); + for (Entry> nextEntry : searchParams.entrySet()) { + for (RuntimeSearchParam nextSp : nextEntry.getValue().values()) { + if (nextSp.getStatus() == RuntimeSearchParamStatusEnum.ACTIVE) { + if (!activeSearchParams.containsKey(nextEntry.getKey())) { + activeSearchParams.put(nextEntry.getKey(), new HashMap()); + } + activeSearchParams.get(nextEntry.getKey()).put(nextSp.getName(), nextSp); + } + } + } + + myActiveSearchParams = activeSearchParams; + + myLastRefresh = System.currentTimeMillis(); + ourLog.info("Refreshed search parameter cache in {}ms", sw.getMillis()); + } + + return myActiveSearchParams.get(theResourceName); + } + + @Override + public void forceRefresh() { + myLastRefresh = 0; + } + + private Map getSearchParamMap(Map> searchParams, String theResourceName) { + Map retVal = searchParams.get(theResourceName); + if (retVal == null) { + retVal = new HashMap(); + searchParams.put(theResourceName, retVal); + } + return retVal; + } + + private RuntimeSearchParam toRuntimeSp(SearchParameter theNextSp) { + String name = theNextSp.getCode(); + String description = theNextSp.getDescription(); + String path = theNextSp.getXpath(); + RestSearchParameterTypeEnum paramType = null; + RuntimeSearchParamStatusEnum status = null; + switch (theNextSp.getType()) { + case COMPOSITE: + paramType = RestSearchParameterTypeEnum.COMPOSITE; + break; + case DATE: + paramType = RestSearchParameterTypeEnum.DATE; + break; + case NUMBER: + paramType = RestSearchParameterTypeEnum.NUMBER; + break; + case QUANTITY: + paramType = RestSearchParameterTypeEnum.QUANTITY; + break; + case REFERENCE: + paramType = RestSearchParameterTypeEnum.REFERENCE; + break; + case STRING: + paramType = RestSearchParameterTypeEnum.STRING; + break; + case TOKEN: + paramType = RestSearchParameterTypeEnum.TOKEN; + break; + case URI: + paramType = RestSearchParameterTypeEnum.URI; + break; + case NULL: + break; + } + if (theNextSp.getStatus() != null) { + switch (theNextSp.getStatus()) { + case ACTIVE: + status = RuntimeSearchParamStatusEnum.ACTIVE; + break; + case DRAFT: + status = RuntimeSearchParamStatusEnum.DRAFT; + break; + case RETIRED: + status = RuntimeSearchParamStatusEnum.RETIRED; + break; + case NULL: + break; + } + } + Set providesMembershipInCompartments = Collections.emptySet(); + Set targets = toStrings(theNextSp.getTarget()); + + if (isBlank(name) || isBlank(path) || paramType == null) { + return null; + } + + RuntimeSearchParam retVal = new RuntimeSearchParam(name, description, path, paramType, providesMembershipInCompartments, targets, status); + return retVal; + } + + private Set toStrings(List theTarget) { + HashSet retVal = new HashSet(); + for (CodeType next : theTarget) { + if (isNotBlank(next.getValue())) { + retVal.add(next.getValue()); + } + } + return retVal; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java index 88d74058923..85dfffa3558 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java @@ -53,6 +53,12 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { private static JpaValidationSupportChainDstu3 ourJpaValidationSupportChainDstu3; private static IFhirResourceDaoValueSet ourValueSetDao; + + @Autowired + @Qualifier("mySearchParameterDaoDstu3") + protected IFhirResourceDao mySearchParameterDao; + @Autowired + protected ISearchParamRegistry mySearchParamRegsitry; // @Autowired // protected HapiWorkerContext myHapiWorkerContext; @Autowired diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchCustomParamTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchCustomParamTest.java new file mode 100644 index 00000000000..f48641a7c5d --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchCustomParamTest.java @@ -0,0 +1,203 @@ +package ca.uhn.fhir.jpa.dao.dstu3; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsInRelativeOrder; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; + +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.util.*; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.dstu3.model.Bundle.BundleType; +import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; +import org.hl7.fhir.dstu3.model.ContactPoint.ContactPointSystem; +import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; +import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType; +import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus; +import org.hl7.fhir.dstu3.model.codesystems.PublicationStatus; +import org.hl7.fhir.dstu3.model.codesystems.SearchParamType; +import org.hl7.fhir.instance.model.SearchParameter.XPathUsageType; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; + +import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.jpa.dao.SearchParameterMap.EverythingModeEnum; +import ca.uhn.fhir.jpa.entity.*; +import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvc; +import ca.uhn.fhir.model.api.IQueryParameterType; +import ca.uhn.fhir.model.api.Include; +import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; +import ca.uhn.fhir.rest.api.SortOrderEnum; +import ca.uhn.fhir.rest.api.SortSpec; +import ca.uhn.fhir.rest.param.*; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.IBundleProvider; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.util.TestUtil; + +@SuppressWarnings("unchecked") +public class FhirResourceDaoDstu3SearchCustomParamTest extends BaseJpaDstu3Test { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3SearchCustomParamTest.class); + + @Test + public void testSearchWithCustomParam() { + + SearchParameter fooSp = new SearchParameter(); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("FOO SP"); + fooSp.setXpath("Patient.gender"); + fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(fooSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Patient pat = new Patient(); + pat.setGender(AdministrativeGender.MALE); + IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless(); + + Patient pat2 = new Patient(); + pat.setGender(AdministrativeGender.FEMALE); + IIdType patId2 = myPatientDao.create(pat2, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + + //Try with custom gender SP + map = new SearchParameterMap(); + map.add("foo", new TokenParam(null, "male")); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(patId.getValue())); + + //Try with normal gender SP + map = new SearchParameterMap(); + map.add("gender", new TokenParam(null, "male")); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(patId.getValue())); + + } + + @Test + public void testSearchWithCustomParamDraft() { + + SearchParameter fooSp = new SearchParameter(); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("FOO SP"); + fooSp.setXpath("Patient.gender"); + fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.DRAFT); + mySearchParameterDao.create(fooSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Patient pat = new Patient(); + pat.setGender(AdministrativeGender.MALE); + IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless(); + + Patient pat2 = new Patient(); + pat.setGender(AdministrativeGender.FEMALE); + IIdType patId2 = myPatientDao.create(pat2, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + + //Try with custom gender SP (should find nothing) + map = new SearchParameterMap(); + map.add("foo", new TokenParam(null, "male")); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, empty()); + + //Try with normal gender SP + map = new SearchParameterMap(); + map.add("gender", new TokenParam(null, "male")); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(patId.getValue())); + + } + + @Test + public void testSearchWithCustomParamNullStatus() { + + SearchParameter fooSp = new SearchParameter(); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("FOO SP"); + fooSp.setXpath("Patient.gender"); + fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(null); + mySearchParameterDao.create(fooSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Patient pat = new Patient(); + pat.setGender(AdministrativeGender.MALE); + IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless(); + + Patient pat2 = new Patient(); + pat.setGender(AdministrativeGender.FEMALE); + IIdType patId2 = myPatientDao.create(pat2, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + + //Try with custom gender SP (should find nothing) + map = new SearchParameterMap(); + map.add("foo", new TokenParam(null, "male")); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, empty()); + + //Try with normal gender SP + map = new SearchParameterMap(); + map.add("gender", new TokenParam(null, "male")); + results = myPatientDao.search(map); + foundResources = toUnqualifiedVersionlessIdValues(results); + assertThat(foundResources, contains(patId.getValue())); + + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/SearchParamExtractorDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/SearchParamExtractorDstu3Test.java index 8fe8330b97c..0de138ed407 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/SearchParamExtractorDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/SearchParamExtractorDstu3Test.java @@ -1,7 +1,9 @@ package ca.uhn.fhir.jpa.dao.dstu3; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import java.util.HashMap; +import java.util.Map; import java.util.Set; import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport; @@ -12,6 +14,9 @@ import org.junit.BeforeClass; import org.junit.Test; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.jpa.dao.ISearchParamRegistry; import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken; import ca.uhn.fhir.jpa.entity.ResourceTable; @@ -37,7 +42,24 @@ public class SearchParamExtractorDstu3Test { Observation obs = new Observation(); obs.addCategory().addCoding().setSystem("SYSTEM").setCode("CODE"); - SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(ourCtx, ourValidationSupport); + ISearchParamRegistry searchParamRegistry = new ISearchParamRegistry() { + @Override + public Map getActiveSearchParams(String theResourceName) { + RuntimeResourceDefinition nextResDef = ourCtx.getResourceDefinition(theResourceName); + Map sps = new HashMap(); + for (RuntimeSearchParam nextSp : nextResDef.getSearchParams()) { + sps.put(nextSp.getName(), nextSp); + } + return sps; + } + + @Override + public void forceRefresh() { + // nothing + } + }; + + SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(ourCtx, ourValidationSupport, searchParamRegistry); Set tokens = extractor.extractSearchParamTokens(new ResourceTable(), obs); assertEquals(1, tokens.size()); ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.iterator().next(); diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/DynamicSearchTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/DynamicSearchTest.java index 07f3ed921e0..cb97cd8a079 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/DynamicSearchTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/DynamicSearchTest.java @@ -22,6 +22,7 @@ import org.junit.Test; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.context.RuntimeSearchParam.RuntimeSearchParamStatusEnum; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu.resource.Conformance; @@ -175,8 +176,8 @@ public class DynamicSearchTest { @Override public List getSearchParameters() { ArrayList retVal = new ArrayList(); - retVal.add(new RuntimeSearchParam("param1", "This is the first parameter", "Patient.param1", RestSearchParameterTypeEnum.STRING, null, null)); - retVal.add(new RuntimeSearchParam("param2", "This is the second parameter", "Patient.param2", RestSearchParameterTypeEnum.DATE, null, null)); + retVal.add(new RuntimeSearchParam("param1", "This is the first parameter", "Patient.param1", RestSearchParameterTypeEnum.STRING, null, null, RuntimeSearchParamStatusEnum.ACTIVE)); + retVal.add(new RuntimeSearchParam("param2", "This is the second parameter", "Patient.param2", RestSearchParameterTypeEnum.DATE, null, null, RuntimeSearchParamStatusEnum.ACTIVE)); return retVal; } diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/SearchDefaultMethodDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/SearchDefaultMethodDstu3Test.java new file mode 100644 index 00000000000..7b50cd0a1be --- /dev/null +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/SearchDefaultMethodDstu3Test.java @@ -0,0 +1,261 @@ +package ca.uhn.fhir.rest.server; + +import static org.hamcrest.Matchers.either; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +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.dstu3.model.HumanName; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.annotation.OptionalParam; +import ca.uhn.fhir.rest.annotation.RawParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.param.StringAndListParam; +import ca.uhn.fhir.util.PortUtil; +import ca.uhn.fhir.util.TestUtil; + +public class SearchDefaultMethodDstu3Test { + + private static CloseableHttpClient ourClient; + private static FhirContext ourCtx = FhirContext.forDstu3(); + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchDefaultMethodDstu3Test.class); + private static int ourPort; + private static Server ourServer; + private static String ourLastMethod; + private static StringAndListParam ourLastParam1; + private static StringAndListParam ourLastParam2; + + @Before + public void before() { + ourLastMethod = null; + ourLastParam1 = null; + ourLastParam2 = null; + ourLastAdditionalParams = null; + } + + @Test + public void testSearchNoParams() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient"); + CloseableHttpResponse status = ourClient.execute(httpGet); + try { + String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(responseContent); + assertEquals(200, status.getStatusLine().getStatusCode()); + + assertThat(ourLastMethod, either(equalTo("search01")).or(equalTo("search02"))); + assertEquals(null, ourLastParam1); + assertEquals(null, ourLastParam2); + assertEquals(null, ourLastAdditionalParams); + + } finally { + IOUtils.closeQuietly(status.getEntity().getContent()); + } + + } + + @Test + public void testSearchOneOptionalParam() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?param1=val1"); + CloseableHttpResponse status = ourClient.execute(httpGet); + try { + String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(responseContent); + assertEquals(200, status.getStatusLine().getStatusCode()); + + assertThat(ourLastParam1.getValuesAsQueryTokens(), hasSize(1)); + assertThat(ourLastParam1.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens(), hasSize(1)); + assertEquals("val1", ourLastParam1.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue()); + assertEquals(null, ourLastParam2); + assertEquals(null, ourLastAdditionalParams); + + } finally { + IOUtils.closeQuietly(status.getEntity().getContent()); + } + + } + + @Test + public void testSearchTwoOptionalParams() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?param1=val1¶m2=val2"); + CloseableHttpResponse status = ourClient.execute(httpGet); + try { + String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(responseContent); + assertEquals(200, status.getStatusLine().getStatusCode()); + + assertThat(ourLastParam1.getValuesAsQueryTokens(), hasSize(1)); + assertThat(ourLastParam1.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens(), hasSize(1)); + assertEquals("val1", ourLastParam1.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue()); + + assertThat(ourLastParam2.getValuesAsQueryTokens(), hasSize(1)); + assertThat(ourLastParam2.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens(), hasSize(1)); + assertEquals("val2", ourLastParam2.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue()); + + assertEquals(null, ourLastAdditionalParams); + + } finally { + IOUtils.closeQuietly(status.getEntity().getContent()); + } + + } + + @Test + public void testSearchTwoOptionalParamsAndExtraParam() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?param1=val1¶m2=val2¶m3=val3&_pretty=true"); + CloseableHttpResponse status = ourClient.execute(httpGet); + try { + String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(responseContent); + assertEquals(200, status.getStatusLine().getStatusCode()); + + assertEquals("search03", ourLastMethod); + + assertThat(ourLastParam1.getValuesAsQueryTokens(), hasSize(1)); + assertThat(ourLastParam1.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens(), hasSize(1)); + assertEquals("val1", ourLastParam1.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue()); + + assertThat(ourLastParam2.getValuesAsQueryTokens(), hasSize(1)); + assertThat(ourLastParam2.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens(), hasSize(1)); + assertEquals("val2", ourLastParam2.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue()); + + ourLog.info(ourLastAdditionalParams.toString()); + assertEquals(1, ourLastAdditionalParams.size()); + assertEquals("val3", ourLastAdditionalParams.get("param3").get(0)); + + } finally { + IOUtils.closeQuietly(status.getEntity().getContent()); + } + + } + + @Test + public void testSearchTwoOptionalParamsWithQualifierAndExtraParam() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?param1=val1¶m2=val2¶m2:exact=val2e¶m3=val3&_pretty=true"); + CloseableHttpResponse status = ourClient.execute(httpGet); + try { + String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(responseContent); + assertEquals(200, status.getStatusLine().getStatusCode()); + + assertEquals("search03", ourLastMethod); + + assertThat(ourLastParam1.getValuesAsQueryTokens(), hasSize(1)); + assertThat(ourLastParam1.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens(), hasSize(1)); + assertEquals("val1", ourLastParam1.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue()); + + assertThat(ourLastParam2.getValuesAsQueryTokens(), hasSize(2)); + assertThat(ourLastParam2.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens(), hasSize(1)); + assertEquals("val2", ourLastParam2.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue()); + assertEquals("val2e", ourLastParam2.getValuesAsQueryTokens().get(1).getValuesAsQueryTokens().get(0).getValue()); + + ourLog.info(ourLastAdditionalParams.toString()); + assertEquals(1, ourLastAdditionalParams.size()); + assertEquals("val3", ourLastAdditionalParams.get("param3").get(0)); + + } finally { + IOUtils.closeQuietly(status.getEntity().getContent()); + } + + } + + @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(); + RestfulServer servlet = new RestfulServer(ourCtx); + servlet.setPagingProvider(new FifoMemoryPagingProvider(10)); + + servlet.setResourceProviders(patientProvider); + ServletHolder servletHolder = new ServletHolder(servlet); + 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(); + + } + + private static Map> ourLastAdditionalParams; + + public static class DummyPatientResourceProvider implements IResourceProvider { + + @Override + public Class getResourceType() { + return Patient.class; + } + + @Search() + public List search01( + @OptionalParam(name = "param1") StringAndListParam theParam1) { + ourLastMethod = "search01"; + ourLastParam1 = theParam1; + ArrayList retVal = new ArrayList(); + retVal.add((Patient) new Patient().addName(new HumanName().setFamily("FAMILY")).setId("1")); + return retVal; + } + + @Search() + public List search02( + @OptionalParam(name = "param1") StringAndListParam theParam1, + @OptionalParam(name = "param2") StringAndListParam theParam2) { + ourLastMethod = "search02"; + ourLastParam1 = theParam1; + ourLastParam2 = theParam2; + ArrayList retVal = new ArrayList(); + retVal.add((Patient) new Patient().addName(new HumanName().setFamily("FAMILY")).setId("1")); + return retVal; + } + + @Search(allowUnknownParams = true) + public List search03( + @OptionalParam(name = "param1") StringAndListParam theParam1, + @OptionalParam(name = "param2") StringAndListParam theParam2, + @RawParam() Map> theAdditionalParams) { + ourLastMethod = "search03"; + ourLastParam1 = theParam1; + ourLastParam2 = theParam2; + ourLastAdditionalParams = theAdditionalParams; + ArrayList retVal = new ArrayList(); + retVal.add((Patient) new Patient().addName(new HumanName().setFamily("FAMILY")).setId("1")); + return retVal; + } + + } + +} diff --git a/hapi-fhir-validation-resources-dstu2.1/.settings/org.eclipse.jdt.core.prefs b/hapi-fhir-validation-resources-dstu2.1/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..60105c1b951 --- /dev/null +++ b/hapi-fhir-validation-resources-dstu2.1/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.6 From 3191c907a3fa12404e3d00e78ccf564c21f1fe02 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Thu, 2 Feb 2017 06:23:28 -0500 Subject: [PATCH 2/7] Work on JPA --- hapi-fhir-android-realm/.project | 23 ++++++ .../ca/uhn/fhir/i18n/hapi-messages.properties | 3 +- .../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 73 ++++++++++++------- .../ca/uhn/fhir/jpa/dao/IFhirResourceDao.java | 15 +++- .../FhirResourceDaoSearchParameterDstu3.java | 15 +++- ...rceProviderCustomSearchParamDstu3Test.java | 56 ++++++++++++++ .../resources/vm/jpa_resource_provider.vm | 3 + 7 files changed, 155 insertions(+), 33 deletions(-) create mode 100644 hapi-fhir-android-realm/.project create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java diff --git a/hapi-fhir-android-realm/.project b/hapi-fhir-android-realm/.project new file mode 100644 index 00000000000..13005c6b849 --- /dev/null +++ b/hapi-fhir-android-realm/.project @@ -0,0 +1,23 @@ + + + hapi-fhir-android-realm + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties index ff82937d610..636b56fc038 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties @@ -43,11 +43,9 @@ ca.uhn.fhir.rest.param.ResourceParameter.noContentTypeInRequest=No Content-Type ca.uhn.fhir.rest.param.ResourceParameter.failedToParseRequest=Failed to parse request body as {0} resource. Error was: {1} ca.uhn.fhir.parser.ParserState.wrongResourceTypeFound=Incorrect resource type found, expected "{0}" but found "{1}" - ca.uhn.fhir.rest.server.RestfulServer.getPagesNonHttpGet=Requests for _getpages must use HTTP GET ca.uhn.fhir.rest.server.RestfulServer.unknownMethod=Invalid request: The FHIR endpoint on this server does not know how to handle {0} operation[{1}] with parameters [{2}] ca.uhn.fhir.rest.server.RestfulServer.rootRequest=This is the base URL of FHIR server. Unable to handle this request, as it does not contain a resource type or operation name. - ca.uhn.fhir.validation.ValidationContext.unableToDetermineEncoding=Unable to determine encoding (e.g. XML / JSON) on validation input. Is this a valid FHIR resource body? ca.uhn.fhir.validation.FhirValidator.noPhlocWarningOnStartup=Phloc-schematron library not found on classpath, will not attempt to perform schematron validation ca.uhn.fhir.validation.FhirValidator.noPhlocError=Phloc-schematron library not found on classpath, can not enable perform schematron validation @@ -81,6 +79,7 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.unableToDeleteNotFound=Unable to fin ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulCreate=Successfully created resource "{0}" in {1}ms ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulUpdate=Successfully updated resource "{0}" in {1}ms ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulDeletes=Successfully deleted {0} resource(s) in {1}ms +ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidSearchParameter=Unknown search parameter "{0}". Value search parameters for this search are: {1} ca.uhn.fhir.jpa.dao.SearchBuilder.invalidQuantityPrefix=Unable to handle quantity prefix "{0}" for value: {1} ca.uhn.fhir.jpa.dao.SearchBuilder.invalidNumberPrefix=Unable to handle number prefix "{0}" for value: {1} 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 5057c12612f..4848c204674 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 @@ -57,6 +57,8 @@ import ca.uhn.fhir.rest.api.PatchTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum; +import ca.uhn.fhir.rest.method.SearchMethodBinding; +import ca.uhn.fhir.rest.method.SearchMethodBinding.QualifierDetails; import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.exceptions.*; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; @@ -73,6 +75,7 @@ public abstract class BaseHapiFhirResourceDao extends B @Autowired private DaoConfig myDaoConfig; + @Autowired protected PlatformTransactionManager myPlatformTransactionManager; @Autowired @@ -86,6 +89,8 @@ public abstract class BaseHapiFhirResourceDao extends B @Autowired() protected ISearchResultDao mySearchResultDao; private String mySecondaryPrimaryKeyParamName; + @Autowired + private ISearchParamRegistry mySerarchParamRegistry; @Autowired() protected IHapiTerminologySvc myTerminologySvc; @@ -662,6 +667,30 @@ public abstract class BaseHapiFhirResourceDao extends B return retVal; } + @Override + public DaoMethodOutcome patch(IIdType theId, PatchTypeEnum thePatchType, String thePatchBody, RequestDetails theRequestDetails) { + ResourceTable entityToUpdate = readEntityLatestVersion(theId); + if (theId.hasVersionIdPart()) { + if (theId.getVersionIdPartAsLong() != entityToUpdate.getVersion()) { + throw new ResourceVersionConflictException("Version " + theId.getVersionIdPart() + " is not the most recent version of this resource, unable to apply patch"); + } + } + + validateResourceType(entityToUpdate); + + IBaseResource resourceToUpdate = toResource(entityToUpdate, false); + IBaseResource destination; + if (thePatchType == PatchTypeEnum.JSON_PATCH) { + destination = JsonPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody); + } else { + destination = XmlPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody); + } + + @SuppressWarnings("unchecked") + T destinationCasted = (T) destination; + return update(destinationCasted, null, true, theRequestDetails); + } + @PostConstruct public void postConstruct() { RuntimeResourceDefinition def = getContext().getResourceDefinition(myResourceType); @@ -863,9 +892,6 @@ public abstract class BaseHapiFhirResourceDao extends B return search(map); } - @Autowired - private ISearchParamRegistry mySerarchParamRegistry; - @Override public IBundleProvider search(final SearchParameterMap theParams) { // Notify interceptors @@ -988,6 +1014,23 @@ public abstract class BaseHapiFhirResourceDao extends B return retVal; } + @Override + public void translateRawParameters(Map> theSource, SearchParameterMap theTarget) { + Map searchParams = mySerarchParamRegistry.getActiveSearchParams(getResourceName()); + + Set paramNames = theSource.keySet(); + for (String nextParamName : paramNames) { + QualifierDetails qualifiedParamName = SearchMethodBinding.extractQualifiersFromParameterName(nextParamName); + RuntimeSearchParam param = searchParams.get(qualifiedParamName.getParamName()); + if (param == null) { + String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "invalidSearchParameter", qualifiedParamName.getParamName(), new TreeSet(searchParams.keySet())); + throw new InvalidRequestException(msg); + } + + aaaa + } + } + @Override public DaoMethodOutcome update(T theResource) { return update(theResource, null, null); @@ -1078,30 +1121,6 @@ public abstract class BaseHapiFhirResourceDao extends B ourLog.info(msg); return outcome; } - - @Override - public DaoMethodOutcome patch(IIdType theId, PatchTypeEnum thePatchType, String thePatchBody, RequestDetails theRequestDetails) { - ResourceTable entityToUpdate = readEntityLatestVersion(theId); - if (theId.hasVersionIdPart()) { - if (theId.getVersionIdPartAsLong() != entityToUpdate.getVersion()) { - throw new ResourceVersionConflictException("Version " + theId.getVersionIdPart() + " is not the most recent version of this resource, unable to apply patch"); - } - } - - validateResourceType(entityToUpdate); - - IBaseResource resourceToUpdate = toResource(entityToUpdate, false); - IBaseResource destination; - if (thePatchType == PatchTypeEnum.JSON_PATCH) { - destination = JsonPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody); - } else { - destination = XmlPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody); - } - - @SuppressWarnings("unchecked") - T destinationCasted = (T) destination; - return update(destinationCasted, null, true, theRequestDetails); - } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java index 4e6469a15ae..5c64e197ff2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java @@ -39,8 +39,10 @@ import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.PatchTypeEnum; import ca.uhn.fhir.rest.api.ValidationModeEnum; import ca.uhn.fhir.rest.method.RequestDetails; +import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.IBundleProvider; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; public interface IFhirResourceDao extends IDao { @@ -134,6 +136,8 @@ public interface IFhirResourceDao extends IDao { */ MT metaGetOperation(Class theType, RequestDetails theRequestDetails); + DaoMethodOutcome patch(IIdType theId, PatchTypeEnum thePatchType, String thePatchBody, RequestDetails theRequestDetails); + Set processMatchUrl(String theMatchUrl); /** @@ -181,6 +185,15 @@ public interface IFhirResourceDao extends IDao { Set searchForIdsWithAndOr(SearchParameterMap theParams); + /** + * Takes a map of incoming raw search parameters and translates/parses them into + * appropriate {@link IQueryParameterType} instances of the appropriate type + * for the given param + * + * @throws InvalidRequestException If any of the parameters are not known + */ + void translateRawParameters(Map> theSource, SearchParameterMap theTarget); + /** * Update a resource - Note that this variant of the method does not take in a {@link RequestDetails} and * therefore can not fire any interceptors. Use only for internal system calls @@ -211,8 +224,6 @@ public interface IFhirResourceDao extends IDao { */ MethodOutcome validate(T theResource, IIdType theId, String theRawResource, EncodingEnum theEncoding, ValidationModeEnum theMode, String theProfile, RequestDetails theRequestDetails); - DaoMethodOutcome patch(IIdType theId, PatchTypeEnum thePatchType, String thePatchBody, RequestDetails theRequestDetails); - // /** // * Invoke the everything operation // */ diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java index cf8fd24e65b..b7ac869fbf9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java @@ -29,17 +29,28 @@ import org.springframework.scheduling.annotation.Scheduled; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSearchParameter; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; public class FhirResourceDaoSearchParameterDstu3 extends FhirResourceDaoDstu3implements IFhirResourceDaoSearchParameter { @Autowired private IFhirSystemDao mySystemDao; + @Override + protected void validateResourceForStorage(SearchParameter theResource, ResourceTable theEntityToSave) { + super.validateResourceForStorage(theResource, theEntityToSave); + + if (theResource.getStatus() == null) { + throw new InvalidRequestException("Resource.status is missing or invalid: " + theResource.getStatusElement().getValueAsString()); + } + } + /** * This method is called once per minute to perform any required re-indexing. During most passes this will * just check and find that there are no resources requiring re-indexing. In that case the method just returns - * immediately. If the search finds that some resources require reindexing, the system will do a bunch of - * reindexing and then return. + * immediately. If the search finds that some resources require reindexing, the system will do multiple + * reindexing passes and then return. */ @Override @Scheduled(fixedDelay=DateUtils.MILLIS_PER_MINUTE) diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java new file mode 100644 index 00000000000..90c479fa782 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java @@ -0,0 +1,56 @@ +package ca.uhn.fhir.jpa.provider.dstu3; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.IOException; + +import org.hl7.fhir.dstu3.model.SearchParameter; +import org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Test; + +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.util.TestUtil; + +public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProviderDstu3Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderCustomSearchParamDstu3Test.class); + + @Override + @After + public void after() throws Exception { + super.after(); + + myDaoConfig.setDefaultSearchParamsCanBeOverridden(new DaoConfig().isDefaultSearchParamsCanBeOverridden()); + } + + @Override + public void before() throws Exception { + super.before(); + } + + @Test + public void saveCreateSearchParamInvalidWithMissingStatus() throws IOException { + SearchParameter sp = new SearchParameter(); + sp.setCode("foo"); + sp.setXpath("Patient.gender"); + sp.setXpathUsage(XPathUsageType.NORMAL); + sp.setTitle("Foo Param"); + + try { + ourClient.create().resource(sp).execute(); + fail(); + } catch (InvalidRequestException e) { + assertEquals("", e.getMessage()); + } + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} 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 212dfb62ee2..df8987b6bf5 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 @@ -99,6 +99,9 @@ public class ${className}ResourceProvider extends #end #end + @RawParam + Map> theAdditionalRawParams, + #if ( $version != 'dstu' ) @IncludeParam(reverse=true) Set theRevIncludes, From 97ff79d730fb86c733facd5899d990ed59cdebbd Mon Sep 17 00:00:00 2001 From: James Date: Thu, 2 Feb 2017 20:37:58 -0500 Subject: [PATCH 3/7] Work on custom params --- HELPWANTED.md | 8 ++ .../fhir/rest/method/SearchMethodBinding.java | 16 +++ .../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 33 +++---- .../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 59 +++++++++-- .../fhir/jpa/dao/FhirResourceDaoDstu2.java | 3 +- .../dao/FhirResourceDaoSubscriptionDstu2.java | 2 +- .../main/java/ca/uhn/fhir/jpa/dao/IDao.java | 4 + .../ca/uhn/fhir/jpa/dao/SearchBuilder.java | 43 ++++---- .../jpa/dao/dstu3/FhirResourceDaoDstu3.java | 3 +- .../FhirResourceDaoSubscriptionDstu3.java | 2 +- .../search/PersistedJpaBundleProvider.java | 4 +- .../uhn/fhir/jpa/dao/BaseHapiFhirDaoTest.java | 12 ++- ...ResourceDaoDstu3SearchCustomParamTest.java | 55 +---------- ...rceProviderCustomSearchParamDstu3Test.java | 98 ++++++++++++++++++- .../resources/vm/jpa_resource_provider.vm | 4 +- 15 files changed, 236 insertions(+), 110 deletions(-) create mode 100644 HELPWANTED.md diff --git a/HELPWANTED.md b/HELPWANTED.md new file mode 100644 index 00000000000..5ef933eda90 --- /dev/null +++ b/HELPWANTED.md @@ -0,0 +1,8 @@ +# Help Wanted + +This page is a work in progress! + +It serves as a place to list potential help a new volunteer could offer. + +* Investigate adding support for FHIR's RDF (Turtle) encoding to HAPI + diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java index 456b24cf304..3ca67891b3f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java @@ -403,19 +403,26 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { retVal.setDotQualifier(theParamName.substring(dotIdx, colonIdx)); retVal.setColonQualifier(theParamName.substring(colonIdx)); retVal.setParamName(theParamName.substring(0, dotIdx)); + retVal.setWholeQualifier(theParamName.substring(dotIdx)); } else { retVal.setColonQualifier(theParamName.substring(colonIdx, dotIdx)); retVal.setDotQualifier(theParamName.substring(dotIdx)); retVal.setParamName(theParamName.substring(0, colonIdx)); + retVal.setWholeQualifier(theParamName.substring(colonIdx)); } } else if (dotIdx != -1) { retVal.setDotQualifier(theParamName.substring(dotIdx)); retVal.setParamName(theParamName.substring(0, dotIdx)); + retVal.setWholeQualifier(theParamName.substring(dotIdx)); } else if (colonIdx != -1) { retVal.setColonQualifier(theParamName.substring(colonIdx)); retVal.setParamName(theParamName.substring(0, colonIdx)); + retVal.setWholeQualifier(theParamName.substring(colonIdx)); } else { retVal.setParamName(theParamName); + retVal.setColonQualifier(null); + retVal.setDotQualifier(null); + retVal.setWholeQualifier(null); } return retVal; @@ -426,6 +433,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { private String myColonQualifier; private String myDotQualifier; private String myParamName; + private String myWholeQualifier; public boolean passes(Set theQualifierWhitelist, Set theQualifierBlacklist) { if (theQualifierWhitelist != null) { @@ -487,6 +495,14 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { myDotQualifier = theDotQualifier; } + public String getWholeQualifier() { + return myWholeQualifier; + } + + public void setWholeQualifier(String theWholeQualifier) { + myWholeQualifier = theWholeQualifier; + } + } public static BaseHttpClientInvocation createSearchInvocation(FhirContext theContext, String theSearchUrl, Map> theParams) { 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 979cdef07f9..624d8feacd4 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 @@ -213,6 +213,9 @@ public abstract class BaseHapiFhirDao implements IDao { @Autowired private ISearchParamExtractor mySearchParamExtractor; + @Autowired + private ISearchParamRegistry mySearchParamRegistry; + @Autowired private ISearchResultDao mySearchResultDao; @@ -943,7 +946,7 @@ public abstract class BaseHapiFhirDao implements IDao { public Set processMatchUrl(String theMatchUrl, Class theResourceType) { RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(theResourceType); - SearchParameterMap paramMap = translateMatchUrl(myContext, theMatchUrl, resourceDef); + SearchParameterMap paramMap = translateMatchUrl(this, myContext, theMatchUrl, resourceDef); paramMap.setPersistResults(false); if (paramMap.isEmpty() && paramMap.getLastUpdated() == null) { @@ -956,30 +959,18 @@ public abstract class BaseHapiFhirDao implements IDao { return ids; } + @Override + public RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName) { + Map params = mySearchParamRegistry.getActiveSearchParams(theResourceDef.getName()); + return params.get(theParamName); + } + @SuppressWarnings("unused") @CoverageIgnore public BaseHasResource readEntity(IIdType theValueId) { throw new NotImplementedException(""); } - // protected MetaDt toMetaDt(Collection tagDefinitions) { - // MetaDt retVal = new MetaDt(); - // for (TagDefinition next : tagDefinitions) { - // switch (next.getTagType()) { - // case PROFILE: - // retVal.addProfile(next.getCode()); - // break; - // case SECURITY_LABEL: - // retVal.addSecurity().setSystem(next.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay()); - // break; - // case TAG: - // retVal.addTag().setSystem(next.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay()); - // break; - // } - // } - // return retVal; - // } - public void setConfig(DaoConfig theConfig) { myConfig = theConfig; } @@ -1718,7 +1709,7 @@ public abstract class BaseHapiFhirDao implements IDao { return parameters; } - public static SearchParameterMap translateMatchUrl(FhirContext theContext, String theMatchUrl, RuntimeResourceDefinition resourceDef) { + public static SearchParameterMap translateMatchUrl(IDao theCallingDao, FhirContext theContext, String theMatchUrl, RuntimeResourceDefinition resourceDef) { SearchParameterMap paramMap = new SearchParameterMap(); List parameters = translateMatchUrl(theMatchUrl); @@ -1788,7 +1779,7 @@ public abstract class BaseHapiFhirDao implements IDao { } else if (nextParamName.startsWith("_")) { // ignore these since they aren't search params (e.g. _sort) } else { - RuntimeSearchParam paramDef = resourceDef.getSearchParam(nextParamName); + RuntimeSearchParam paramDef = theCallingDao.getSearchParamByName(resourceDef, nextParamName); if (paramDef == null) { throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName); } 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 4848c204674..ff906ce03ca 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 @@ -22,15 +22,29 @@ package ca.uhn.fhir.jpa.dao; import static org.apache.commons.lang3.StringUtils.isNotBlank; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeSet; import javax.annotation.PostConstruct; import javax.persistence.NoResultException; import javax.persistence.TypedQuery; import org.hl7.fhir.dstu3.model.IdType; -import org.hl7.fhir.instance.model.api.*; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBaseCoding; +import org.hl7.fhir.instance.model.api.IBaseMetaType; +import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Required; import org.springframework.transaction.PlatformTransactionManager; @@ -44,23 +58,42 @@ import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; -import ca.uhn.fhir.jpa.entity.*; +import ca.uhn.fhir.jpa.entity.BaseHasResource; +import ca.uhn.fhir.jpa.entity.BaseTag; +import ca.uhn.fhir.jpa.entity.ResourceHistoryTable; +import ca.uhn.fhir.jpa.entity.ResourceLink; +import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.jpa.entity.TagDefinition; +import ca.uhn.fhir.jpa.entity.TagTypeEnum; import ca.uhn.fhir.jpa.interceptor.IJpaServerInterceptor; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.jpa.util.DeleteConflict; import ca.uhn.fhir.jpa.util.StopWatch; import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils; import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils; -import ca.uhn.fhir.model.api.*; +import ca.uhn.fhir.model.api.IQueryParameterAnd; +import ca.uhn.fhir.model.api.IQueryParameterType; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.Include; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.PatchTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.method.MethodUtil; +import ca.uhn.fhir.rest.method.QualifiedParamList; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.method.SearchMethodBinding; import ca.uhn.fhir.rest.method.SearchMethodBinding.QualifierDetails; import ca.uhn.fhir.rest.server.IBundleProvider; -import ca.uhn.fhir.rest.server.exceptions.*; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +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.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.util.FhirTerser; @@ -697,7 +730,7 @@ public abstract class BaseHapiFhirResourceDao extends B myResourceName = def.getName(); if (mySecondaryPrimaryKeyParamName != null) { - RuntimeSearchParam sp = def.getSearchParam(mySecondaryPrimaryKeyParamName); + RuntimeSearchParam sp = getSearchParamByName(def, mySecondaryPrimaryKeyParamName); if (sp == null) { throw new ConfigurationException("Unknown search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "]"); } @@ -1026,8 +1059,20 @@ public abstract class BaseHapiFhirResourceDao extends B String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "invalidSearchParameter", qualifiedParamName.getParamName(), new TreeSet(searchParams.keySet())); throw new InvalidRequestException(msg); } + + // Should not be null since the check above would have caught it + RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(myResourceName); + RuntimeSearchParam paramDef = getSearchParamByName(resourceDef, qualifiedParamName.getParamName()); + + for (String nextValue : theSource.get(nextParamName)) { + if (isNotBlank(nextValue)) { + QualifiedParamList qualifiedParam = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(qualifiedParamName.getWholeQualifier(), nextValue); + List paramList = Collections.singletonList(qualifiedParam); + IQueryParameterAnd parsedParam = MethodUtil.parseQueryParams(getContext(), paramDef, nextParamName, paramList); + theTarget.add(qualifiedParamName.getParamName(), parsedParam); + } + } - aaaa } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2.java index 46841016ae5..34b82976b90 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2.java @@ -79,7 +79,8 @@ public class FhirResourceDaoDstu2 extends BaseHapiFhirResou values.addAll(theTerser.getAllPopulatedChildElementsOfType(theResource, BaseResourceReferenceDt.class)); } else if (theInclude.getValue().startsWith(theResourceDef.getName() + ":")) { values = new ArrayList(); - RuntimeSearchParam sp = theResourceDef.getSearchParam(theInclude.getValue().substring(theInclude.getValue().indexOf(':') + 1)); + String paramName = theInclude.getValue().substring(theInclude.getValue().indexOf(':') + 1); + RuntimeSearchParam sp = getSearchParamByName(theResourceDef, paramName); for (String nextPath : sp.getPathsSplit()) { values.addAll(theTerser.getValues(theResource, nextPath)); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java index 50f31cf6e8f..3434d3fc41a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java @@ -152,7 +152,7 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2 R toResource(Class theResourceType, BaseHasResource theEntity, boolean theForHistoryOperation); void populateFullTextFields(IBaseResource theResource, ResourceTable theEntity); + + RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java index a43e31f50c8..3106f9bc0b8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java @@ -216,12 +216,13 @@ public class SearchBuilder { throw new InvalidRequestException("Invalid resource type: " + targetResourceType); } - RuntimeSearchParam owningParameterDef = targetResourceDefinition.getSearchParam(parameterName.replaceAll("\\..*", "")); + String paramName = parameterName.replaceAll("\\..*", ""); + RuntimeSearchParam owningParameterDef = myCallingDao.getSearchParamByName(targetResourceDefinition, paramName); if (owningParameterDef == null) { throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + parameterName); } - owningParameterDef = targetResourceDefinition.getSearchParam(owningParameter); + owningParameterDef = myCallingDao.getSearchParamByName(targetResourceDefinition, owningParameter); if (owningParameterDef == null) { throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + owningParameter); } @@ -241,7 +242,7 @@ public class SearchBuilder { List predicates = new ArrayList(); predicates.add(builder.equal(from.get("mySourceResourceType"), targetResourceType)); predicates.add(from.get("mySourceResourcePid").in(match)); - predicates.add(createResourceLinkPathPredicate(myContext, owningParameter, from, resourceType)); + predicates.add(createResourceLinkPathPredicate(myCallingDao, myContext, owningParameter, from, resourceType)); predicates.add(builder.equal(from.get("myTargetResourceType"), myResourceName)); createPredicateResourceId(builder, cq, predicates, from.get("myId").as(Long.class)); createPredicateLastUpdatedForResourceLink(builder, from, predicates); @@ -552,7 +553,8 @@ public class SearchBuilder { String resourceId; if (!ref.getValue().matches("[a-zA-Z]+\\/.*")) { - String paramPath = myContext.getResourceDefinition(myResourceType).getSearchParam(theParamName).getPath(); + RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceType); + String paramPath = myCallingDao.getSearchParamByName(resourceDef, theParamName).getPath(); if (paramPath.endsWith(".as(Reference)")) { paramPath = paramPath.substring(0, paramPath.length() - ".as(Reference)".length()) + "Reference"; } @@ -606,7 +608,7 @@ public class SearchBuilder { boolean isMeta = BaseHapiFhirDao.RESOURCE_META_PARAMS.containsKey(chain); RuntimeSearchParam param = null; if (!isMeta) { - param = typeDef.getSearchParam(chain); + param = myCallingDao.getSearchParamByName(typeDef, chain); if (param == null) { ourLog.debug("Type {} doesn't have search param {}", nextType.getSimpleName(), param); continue; @@ -1385,7 +1387,7 @@ public class SearchBuilder { } private Predicate createResourceLinkPathPredicate(String theParamName, Root from) { - return createResourceLinkPathPredicate(myContext, theParamName, from, myResourceType); + return createResourceLinkPathPredicate(myCallingDao, myContext, theParamName, from, myResourceType); } private TypedQuery createSearchAllByTypeQuery(DateRangeParam theLastUpdated) { @@ -1437,7 +1439,8 @@ public class SearchBuilder { return; } - RuntimeSearchParam param = getSearchParam(theSort.getParamName()); + RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceName); + RuntimeSearchParam param = myCallingDao.getSearchParamByName(resourceDef, theSort.getParamName()); if (param == null) { throw new InvalidRequestException("Unknown sort parameter '" + theSort.getParamName() + "'"); } @@ -1503,7 +1506,8 @@ public class SearchBuilder { private String determineSystemIfMissing(String theParamName, String code, String system) { if (system == null) { - RuntimeSearchParam param = getSearchParam(theParamName); + RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceName); + RuntimeSearchParam param = myCallingDao.getSearchParamByName(resourceDef, theParamName); if (param != null) { Set valueSetUris = Sets.newHashSet(); for (String nextPath : param.getPathsSplit()) { @@ -1612,12 +1616,6 @@ public class SearchBuilder { doSetPids(resultList); } - private RuntimeSearchParam getSearchParam(String theParamName) { - RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceType); - RuntimeSearchParam param = resourceDef.getSearchParam(theParamName); - return param; - } - private void loadResourcesByPid(Collection theIncludePids, List theResourceListToPopulate, Set theRevIncludedPids, boolean theForHistoryOperation) { EntityManager entityManager = myEntityManager; FhirContext context = myContext; @@ -2042,8 +2040,9 @@ public class SearchBuilder { return likeExpression.replace("%", "[%]") + "%"; } - private static Predicate createResourceLinkPathPredicate(FhirContext theContext, String theParamName, Root from, Class resourceType) { - RuntimeSearchParam param = theContext.getResourceDefinition(resourceType).getSearchParam(theParamName); + private static Predicate createResourceLinkPathPredicate(IDao theCallingDao, FhirContext theContext, String theParamName, Root from, Class resourceType) { + RuntimeResourceDefinition resourceDef = theContext.getResourceDefinition(resourceType); + RuntimeSearchParam param = theCallingDao.getSearchParamByName(resourceDef, theParamName); List path = param.getPathsSplit(); Predicate type = from.get("mySourcePath").in(path); return type; @@ -2114,7 +2113,7 @@ public class SearchBuilder { * * @param theLastUpdated */ - public static HashSet loadReverseIncludes(FhirContext theContext, EntityManager theEntityManager, Collection theMatches, Set theRevIncludes, boolean theReverseMode, DateRangeParam theLastUpdated) { + public static HashSet loadReverseIncludes(IDao theCallingDao, FhirContext theContext, EntityManager theEntityManager, Collection theMatches, Set theRevIncludes, boolean theReverseMode, DateRangeParam theLastUpdated) { if (theMatches.size() == 0) { return new HashSet(); } @@ -2182,7 +2181,11 @@ public class SearchBuilder { } String paramName = nextInclude.getParamName(); - param = isNotBlank(paramName) ? def.getSearchParam(paramName) : null; + if (isNotBlank(paramName)) { + param = theCallingDao.getSearchParamByName(def, paramName); + } else { + param = null; + } if (param == null) { ourLog.warn("Unknown param name in include/revinclude=" + nextInclude.getValue()); continue; @@ -2283,9 +2286,9 @@ public class SearchBuilder { Set revIncludedPids = new HashSet(); if (myParams.getEverythingMode() == null) { - revIncludedPids.addAll(loadReverseIncludes(myContext, myEntityManager, pidsSubList, myParams.getRevIncludes(), true, myParams.getLastUpdated())); + revIncludedPids.addAll(loadReverseIncludes(myCallingDao, myContext, myEntityManager, pidsSubList, myParams.getRevIncludes(), true, myParams.getLastUpdated())); } - revIncludedPids.addAll(loadReverseIncludes(myContext, myEntityManager, pidsSubList, myParams.getIncludes(), false, myParams.getLastUpdated())); + revIncludedPids.addAll(loadReverseIncludes(myCallingDao, myContext, myEntityManager, pidsSubList, myParams.getIncludes(), false, myParams.getLastUpdated())); // Execute the query and make sure we return distinct results List resources = new ArrayList(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3.java index c333d685e0d..571c5e06c99 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3.java @@ -92,7 +92,8 @@ public class FhirResourceDaoDstu3 extends BaseHapiFhirRe values.addAll(theTerser.getAllPopulatedChildElementsOfType(theResource, BaseResourceReferenceDt.class)); } else if (theInclude.getValue().startsWith(theResourceDef.getName() + ":")) { values = new ArrayList(); - RuntimeSearchParam sp = theResourceDef.getSearchParam(theInclude.getValue().substring(theInclude.getValue().indexOf(':') + 1)); + String paramName = theInclude.getValue().substring(theInclude.getValue().indexOf(':') + 1); + RuntimeSearchParam sp = getSearchParamByName(theResourceDef, paramName); for (String nextPath : sp.getPathsSplit()) { values.addAll(theTerser.getValues(theResource, nextPath)); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSubscriptionDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSubscriptionDstu3.java index 43b8ec3d92b..5bcb0a63416 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSubscriptionDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSubscriptionDstu3.java @@ -157,7 +157,7 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3 revIncludedPids = new HashSet(); if (mySearchEntity.getSearchType() == SearchTypeEnum.SEARCH) { - revIncludedPids.addAll(SearchBuilder.loadReverseIncludes(myContext, myEntityManager, pidsSubList, mySearchEntity.toRevIncludesList(), true, mySearchEntity.getLastUpdated())); + revIncludedPids.addAll(SearchBuilder.loadReverseIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toRevIncludesList(), true, mySearchEntity.getLastUpdated())); } - revIncludedPids.addAll(SearchBuilder.loadReverseIncludes(myContext, myEntityManager, pidsSubList, mySearchEntity.toIncludesList(), false, mySearchEntity.getLastUpdated())); + revIncludedPids.addAll(SearchBuilder.loadReverseIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toIncludesList(), false, mySearchEntity.getLastUpdated())); // Execute the query and make sure we return distinct results List resources = new ArrayList(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDaoTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDaoTest.java index c2f3db0c372..5d2fd425e4b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDaoTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDaoTest.java @@ -1,11 +1,16 @@ package ca.uhn.fhir.jpa.dao; import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import org.junit.AfterClass; import org.junit.Test; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.model.dstu2.composite.PeriodDt; import ca.uhn.fhir.model.dstu2.resource.Condition; import ca.uhn.fhir.model.dstu2.resource.Observation; @@ -25,7 +30,12 @@ public class BaseHapiFhirDaoTest extends BaseJpaTest { @Test public void testTranslateMatchUrl() { - SearchParameterMap match = BaseHapiFhirDao.translateMatchUrl(ourCtx, "Condition?patient=304&_lastUpdated=>2011-01-01T11:12:21.0000Z", ourCtx.getResourceDefinition(Condition.class)); + RuntimeResourceDefinition resourceDef = ourCtx.getResourceDefinition(Condition.class); + + IDao dao = mock(IDao.class); + when(dao.getSearchParamByName(any(RuntimeResourceDefinition.class), eq("patient"))).thenReturn(resourceDef.getSearchParam("patient")); + + SearchParameterMap match = BaseHapiFhirDao.translateMatchUrl(dao, ourCtx, "Condition?patient=304&_lastUpdated=>2011-01-01T11:12:21.0000Z", resourceDef); assertEquals("2011-01-01T11:12:21.0000Z", match.getLastUpdated().getLowerBound().getValueAsString()); assertEquals(ReferenceParam.class, match.get("patient").get(0).get(0).getClass()); assertEquals("304", ((ReferenceParam)match.get("patient").get(0).get(0)).getIdPart()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchCustomParamTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchCustomParamTest.java index f48641a7c5d..2b9b4303e56 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchCustomParamTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchCustomParamTest.java @@ -1,70 +1,23 @@ package ca.uhn.fhir.jpa.dao.dstu3; import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.containsInRelativeOrder; import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.endsWith; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.hasItems; -import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import java.io.IOException; -import java.math.BigDecimal; -import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.List; -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; -import org.hl7.fhir.dstu3.model.*; -import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; -import org.hl7.fhir.dstu3.model.Bundle.BundleType; -import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; -import org.hl7.fhir.dstu3.model.ContactPoint.ContactPointSystem; import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; -import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType; -import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus; -import org.hl7.fhir.dstu3.model.codesystems.PublicationStatus; -import org.hl7.fhir.dstu3.model.codesystems.SearchParamType; -import org.hl7.fhir.instance.model.SearchParameter.XPathUsageType; -import org.hl7.fhir.instance.model.api.IAnyResource; -import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.SearchParameter; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.junit.After; import org.junit.AfterClass; -import org.junit.Ignore; import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.TransactionCallback; -import org.springframework.transaction.support.TransactionTemplate; -import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; -import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.SearchParameterMap; -import ca.uhn.fhir.jpa.dao.SearchParameterMap.EverythingModeEnum; -import ca.uhn.fhir.jpa.entity.*; -import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvc; -import ca.uhn.fhir.model.api.IQueryParameterType; -import ca.uhn.fhir.model.api.Include; -import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; -import ca.uhn.fhir.rest.api.SortOrderEnum; -import ca.uhn.fhir.rest.api.SortSpec; -import ca.uhn.fhir.rest.param.*; -import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.IBundleProvider; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.TestUtil; -@SuppressWarnings("unchecked") public class FhirResourceDaoDstu3SearchCustomParamTest extends BaseJpaDstu3Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3SearchCustomParamTest.class); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java index 90c479fa782..4494fc86423 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java @@ -1,24 +1,34 @@ package ca.uhn.fhir.jpa.provider.dstu3; +import static org.hamcrest.Matchers.contains; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import java.io.IOException; +import java.util.List; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; +import org.hl7.fhir.dstu3.model.Observation; +import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.SearchParameter; import org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType; +import org.hl7.fhir.instance.model.api.IIdType; import org.junit.After; import org.junit.AfterClass; import org.junit.Test; import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.rest.gclient.ReferenceClientParam; +import ca.uhn.fhir.rest.gclient.TokenClientParam; +import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.TestUtil; public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProviderDstu3Test { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderCustomSearchParamDstu3Test.class); - @Override @After public void after() throws Exception { @@ -44,10 +54,92 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv ourClient.create().resource(sp).execute(); fail(); } catch (InvalidRequestException e) { - assertEquals("", e.getMessage()); + assertEquals("HTTP 400 Bad Request: Resource.status is missing or invalid: null", e.getMessage()); } } + @SuppressWarnings("unused") + @Test + public void testSearchWithCustomParam() { + + SearchParameter fooSp = new SearchParameter(); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("FOO SP"); + fooSp.setXpath("Patient.gender"); + fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(fooSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Patient pat = new Patient(); + pat.setGender(AdministrativeGender.MALE); + IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless(); + + Patient pat2 = new Patient(); + pat.setGender(AdministrativeGender.FEMALE); + IIdType patId2 = myPatientDao.create(pat2, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + Bundle result; + + result = ourClient + .search() + .forResource(Patient.class) + .where(new TokenClientParam("foo").exactly().code("male")) + .returnBundle(Bundle.class) + .execute(); + foundResources = toUnqualifiedVersionlessIdValues(result); + assertThat(foundResources, contains(patId.getValue())); + + } + + @SuppressWarnings("unused") + @Test + public void testSearchQualifiedWithCustomReferenceParam() { + + SearchParameter fooSp = new SearchParameter(); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.REFERENCE); + fooSp.setTitle("FOO SP"); + fooSp.setXpath("Observation.subject"); + fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(fooSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + Patient pat = new Patient(); + pat.setGender(AdministrativeGender.MALE); + IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs1 = new Observation(); + obs1.getSubject().setReferenceElement(patId); + IIdType obsId1 = myObservationDao.create(obs1, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs2 = new Observation(); + obs2.setStatus(org.hl7.fhir.dstu3.model.Observation.ObservationStatus.FINAL); + IIdType obsId2 = myObservationDao.create(obs2, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap map; + IBundleProvider results; + List foundResources; + Bundle result; + + result = ourClient + .search() + .forResource(Observation.class) + .where(new ReferenceClientParam("foo").hasChainedProperty(Patient.GENDER.exactly().code("male"))) + .returnBundle(Bundle.class) + .execute(); + foundResources = toUnqualifiedVersionlessIdValues(result); + assertThat(foundResources, contains(obsId1.getValue())); + + } + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); 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 df8987b6bf5..58abd420a19 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 @@ -39,7 +39,7 @@ public class ${className}ResourceProvider extends return ${className}.class; } - @Search() + @Search(allowUnknownParams=true) public ca.uhn.fhir.rest.server.IBundleProvider search( javax.servlet.http.HttpServletRequest theServletRequest, @@ -154,6 +154,8 @@ public class ${className}ResourceProvider extends paramMap.setCount(theCount); paramMap.setRequestDetails(theRequestDetails); + getDao().translateRawParameters(theAdditionalRawParams, paramMap); + ca.uhn.fhir.rest.server.IBundleProvider retVal = getDao().search(paramMap); return retVal; } finally { From aa5588826e1b8d1a1688b9ee43a17f43b027bf3d Mon Sep 17 00:00:00 2001 From: James Agnew Date: Thu, 2 Feb 2017 22:31:34 -0500 Subject: [PATCH 4/7] Work on custom search params --- .../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 80 +-------- .../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 4 +- .../jpa/dao/BaseSearchParamExtractor.java | 2 +- .../fhir/jpa/dao/data/IResourceTableDao.java | 11 +- .../FhirResourceDaoSearchParameterDstu3.java | 82 +++++++-- .../ca/uhn/fhir/jpa/entity/ResourceTable.java | 1 + ...eDaoDstu3SearchCustomSearchParamTest.java} | 158 +++++++++++------- 7 files changed, 194 insertions(+), 144 deletions(-) rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/{FhirResourceDaoDstu3SearchCustomParamTest.java => FhirResourceDaoDstu3SearchCustomSearchParamTest.java} (53%) 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 624d8feacd4..f9d43602500 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 @@ -25,24 +25,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.io.UnsupportedEncodingException; import java.text.Normalizer; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; +import java.util.*; -import javax.persistence.EntityManager; -import javax.persistence.NoResultException; -import javax.persistence.PersistenceContext; -import javax.persistence.PersistenceContextType; -import javax.persistence.Tuple; -import javax.persistence.TypedQuery; +import javax.persistence.*; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; @@ -58,64 +43,22 @@ import org.apache.http.client.utils.URLEncodedUtils; import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.dstu3.model.StringType; -import org.hl7.fhir.instance.model.api.IAnyResource; -import org.hl7.fhir.instance.model.api.IBase; -import org.hl7.fhir.instance.model.api.IBaseBundle; -import org.hl7.fhir.instance.model.api.IBaseCoding; -import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; -import org.hl7.fhir.instance.model.api.IBaseReference; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IDomainResource; -import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.hl7.fhir.instance.model.api.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.PlatformTransactionManager; import com.google.common.base.Charsets; import com.google.common.collect.ArrayListMultimap; -import ca.uhn.fhir.context.BaseRuntimeChildDefinition; -import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; -import ca.uhn.fhir.context.BaseRuntimeElementDefinition; -import ca.uhn.fhir.context.ConfigurationException; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.context.RuntimeChildResourceDefinition; -import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.context.*; import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; -import ca.uhn.fhir.jpa.entity.BaseHasResource; -import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam; -import ca.uhn.fhir.jpa.entity.BaseTag; -import ca.uhn.fhir.jpa.entity.ForcedId; -import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum; -import ca.uhn.fhir.jpa.entity.ResourceHistoryTable; -import ca.uhn.fhir.jpa.entity.ResourceHistoryTag; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamQuantity; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri; -import ca.uhn.fhir.jpa.entity.ResourceLink; -import ca.uhn.fhir.jpa.entity.ResourceTable; -import ca.uhn.fhir.jpa.entity.ResourceTag; -import ca.uhn.fhir.jpa.entity.Search; -import ca.uhn.fhir.jpa.entity.SearchTypeEnum; -import ca.uhn.fhir.jpa.entity.TagDefinition; -import ca.uhn.fhir.jpa.entity.TagTypeEnum; +import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.util.DeleteConflict; -import ca.uhn.fhir.model.api.IQueryParameterAnd; -import ca.uhn.fhir.model.api.IQueryParameterType; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; -import ca.uhn.fhir.model.api.Tag; -import ca.uhn.fhir.model.api.TagList; +import ca.uhn.fhir.model.api.*; import ca.uhn.fhir.model.base.composite.BaseCodingDt; import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; import ca.uhn.fhir.model.dstu.resource.BaseResource; @@ -133,12 +76,7 @@ import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IBundleProvider; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; -import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; -import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; -import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.fhir.rest.server.exceptions.*; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; @@ -917,7 +855,7 @@ public abstract class BaseHapiFhirDao implements IDao { } /** - * Subclasses may override to provide behaviour. Called when a resource has been inserved into the database for the first time. + * Subclasses may override to provide behaviour. Called when a resource has been inserted into the database for the first time. * * @param theEntity * The entity being updated (Do not modify the entity! Undefined behaviour will occur!) @@ -931,7 +869,7 @@ public abstract class BaseHapiFhirDao implements IDao { } /** - * Subclasses may override to provide behaviour. Called when a resource has been inserved into the database for the first time. + * Subclasses may override to provide behaviour. Called when a pre-existing resource has been updated in the database * * @param theEntity * The resource 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 ff906ce03ca..63e74ffcd5c 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 @@ -57,6 +57,7 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao; +import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; import ca.uhn.fhir.jpa.entity.BaseHasResource; import ca.uhn.fhir.jpa.entity.BaseTag; @@ -108,7 +109,8 @@ public abstract class BaseHapiFhirResourceDao extends B @Autowired private DaoConfig myDaoConfig; - + @Autowired + protected IResourceTableDao myResourceTableDao; @Autowired protected PlatformTransactionManager myPlatformTransactionManager; @Autowired diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamExtractor.java index 61e825139e8..3ff1ce576e4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamExtractor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamExtractor.java @@ -40,7 +40,7 @@ import ca.uhn.fhir.util.FhirTerser; public abstract class BaseSearchParamExtractor implements ISearchParamExtractor { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseSearchParamExtractor.class); - protected static final Pattern SPLIT = Pattern.compile("\\||( or )"); + public static final Pattern SPLIT = Pattern.compile("\\||( or )"); @Autowired private FhirContext myContext; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTableDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTableDao.java index a2fa17bb566..ee21079fe49 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTableDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTableDao.java @@ -10,7 +10,7 @@ package ca.uhn.fhir.jpa.dao.data; * 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 + * 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, @@ -21,9 +21,16 @@ package ca.uhn.fhir.jpa.dao.data; */ import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import ca.uhn.fhir.jpa.entity.ResourceTable; public interface IResourceTableDao extends JpaRepository { - // nothing yet + + @Modifying + @Query("UPDATE ResourceTable r SET r.myIndexStatus = null WHERE r.myResourceType = :restype") + int markResourcesOfTypeAsRequiringReindexing(@Param("restype") String theResourceType); + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java index b7ac869fbf9..82fea85443d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.jpa.dao.dstu3; +import static org.apache.commons.lang3.StringUtils.isBlank; + /* * #%L * HAPI FHIR JPA Server @@ -10,7 +12,7 @@ package ca.uhn.fhir.jpa.dao.dstu3; * 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 + * 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, @@ -27,23 +29,27 @@ import org.hl7.fhir.dstu3.model.SearchParameter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; +import ca.uhn.fhir.jpa.dao.BaseSearchParamExtractor; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSearchParameter; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; -public class FhirResourceDaoSearchParameterDstu3 extends FhirResourceDaoDstu3implements IFhirResourceDaoSearchParameter { +public class FhirResourceDaoSearchParameterDstu3 extends FhirResourceDaoDstu3 implements IFhirResourceDaoSearchParameter { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSearchParameterDstu3.class); @Autowired private IFhirSystemDao mySystemDao; - - @Override - protected void validateResourceForStorage(SearchParameter theResource, ResourceTable theEntityToSave) { - super.validateResourceForStorage(theResource, theEntityToSave); - - if (theResource.getStatus() == null) { - throw new InvalidRequestException("Resource.status is missing or invalid: " + theResource.getStatusElement().getValueAsString()); - } + + private void markAffectedResources(SearchParameter theResource) { + String xpath = theResource.getXpath(); + String resourceType = xpath.substring(0, xpath.indexOf('.')); + ourLog.info("Marking all resources of type {} for reindexing due to updated search parameter with path: {}", xpath); + int updatedCount = myResourceTableDao.markResourcesOfTypeAsRequiringReindexing(resourceType); + ourLog.info("Marked {} resources for reindexing", updatedCount); } /** @@ -53,7 +59,7 @@ public class FhirResourceDaoSearchParameterDstu3 extends FhirResourceDaoDstu3 foundResources; - - //Try with custom gender SP + + // Try with custom gender SP map = new SearchParameterMap(); map.add("foo", new TokenParam(null, "male")); results = myPatientDao.search(map); foundResources = toUnqualifiedVersionlessIdValues(results); assertThat(foundResources, contains(patId.getValue())); - //Try with normal gender SP + // Try with normal gender SP map = new SearchParameterMap(); map.add("gender", new TokenParam(null, "male")); results = myPatientDao.search(map); foundResources = toUnqualifiedVersionlessIdValues(results); assertThat(foundResources, contains(patId.getValue())); - + } @Test @@ -74,9 +163,9 @@ public class FhirResourceDaoDstu3SearchCustomParamTest extends BaseJpaDstu3Test fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL); fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.DRAFT); mySearchParameterDao.create(fooSp, mySrd); - + mySearchParamRegsitry.forceRefresh(); - + Patient pat = new Patient(); pat.setGender(AdministrativeGender.MALE); IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless(); @@ -88,63 +177,21 @@ public class FhirResourceDaoDstu3SearchCustomParamTest extends BaseJpaDstu3Test SearchParameterMap map; IBundleProvider results; List foundResources; - - //Try with custom gender SP (should find nothing) + + // Try with custom gender SP (should find nothing) map = new SearchParameterMap(); map.add("foo", new TokenParam(null, "male")); results = myPatientDao.search(map); foundResources = toUnqualifiedVersionlessIdValues(results); assertThat(foundResources, empty()); - //Try with normal gender SP + // Try with normal gender SP map = new SearchParameterMap(); map.add("gender", new TokenParam(null, "male")); results = myPatientDao.search(map); foundResources = toUnqualifiedVersionlessIdValues(results); assertThat(foundResources, contains(patId.getValue())); - - } - - @Test - public void testSearchWithCustomParamNullStatus() { - SearchParameter fooSp = new SearchParameter(); - fooSp.setCode("foo"); - fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN); - fooSp.setTitle("FOO SP"); - fooSp.setXpath("Patient.gender"); - fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL); - fooSp.setStatus(null); - mySearchParameterDao.create(fooSp, mySrd); - - mySearchParamRegsitry.forceRefresh(); - - Patient pat = new Patient(); - pat.setGender(AdministrativeGender.MALE); - IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless(); - - Patient pat2 = new Patient(); - pat.setGender(AdministrativeGender.FEMALE); - IIdType patId2 = myPatientDao.create(pat2, mySrd).getId().toUnqualifiedVersionless(); - - SearchParameterMap map; - IBundleProvider results; - List foundResources; - - //Try with custom gender SP (should find nothing) - map = new SearchParameterMap(); - map.add("foo", new TokenParam(null, "male")); - results = myPatientDao.search(map); - foundResources = toUnqualifiedVersionlessIdValues(results); - assertThat(foundResources, empty()); - - //Try with normal gender SP - map = new SearchParameterMap(); - map.add("gender", new TokenParam(null, "male")); - results = myPatientDao.search(map); - foundResources = toUnqualifiedVersionlessIdValues(results); - assertThat(foundResources, contains(patId.getValue())); - } @AfterClass @@ -152,5 +199,4 @@ public class FhirResourceDaoDstu3SearchCustomParamTest extends BaseJpaDstu3Test TestUtil.clearAllStaticFieldsForUnitTest(); } - } From 12dc4d1c1162d48b3784fb07ebdc349a6eaf5f31 Mon Sep 17 00:00:00 2001 From: James Date: Sat, 4 Feb 2017 07:14:21 -0500 Subject: [PATCH 5/7] Work on custom params --- hapi-fhir-base-test-mindeps-client/.classpath | 23 ++++++----- hapi-fhir-base-test-mindeps-client/.project | 12 +++++- .../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 4 ++ ...rceProviderCustomSearchParamDstu3Test.java | 38 ++++++++++++++++++- 4 files changed, 65 insertions(+), 12 deletions(-) diff --git a/hapi-fhir-base-test-mindeps-client/.classpath b/hapi-fhir-base-test-mindeps-client/.classpath index d1d9fe67b77..9d7b3cb8dc2 100644 --- a/hapi-fhir-base-test-mindeps-client/.classpath +++ b/hapi-fhir-base-test-mindeps-client/.classpath @@ -1,16 +1,21 @@ - - + + + + + + + + + + + + + - - - - - - - + diff --git a/hapi-fhir-base-test-mindeps-client/.project b/hapi-fhir-base-test-mindeps-client/.project index 9cf5c48e336..096c7c04956 100644 --- a/hapi-fhir-base-test-mindeps-client/.project +++ b/hapi-fhir-base-test-mindeps-client/.project @@ -1,7 +1,7 @@ - hapi-fhir-base-testmindeps-client - + hapi-fhir-base-test-mindeps-client + NO_M2ECLIPSE_SUPPORT: Project files created with the maven-eclipse-plugin are not supported in M2Eclipse. @@ -10,6 +10,11 @@ + + org.eclipse.jdt.core.javabuilder + + + org.eclipse.m2e.core.maven2Builder @@ -17,7 +22,10 @@ + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.wst.common.modulecore.ModuleCoreNature org.eclipse.m2e.core.maven2Nature + org.eclipse.jdt.core.javanature org.eclipse.wst.common.project.facet.core.nature 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 63e74ffcd5c..337e0ad8321 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 @@ -1051,6 +1051,10 @@ public abstract class BaseHapiFhirResourceDao extends B @Override public void translateRawParameters(Map> theSource, SearchParameterMap theTarget) { + if (theSource == null || theSource.isEmpty()) { + return; + } + Map searchParams = mySerarchParamRegistry.getActiveSearchParams(getResourceName()); Set paramNames = theSource.keySet(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java index 4494fc86423..695af8eda4a 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java @@ -11,6 +11,7 @@ import java.util.List; import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; import org.hl7.fhir.dstu3.model.Observation; +import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.SearchParameter; import org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType; @@ -19,8 +20,10 @@ import org.junit.After; import org.junit.AfterClass; import org.junit.Test; +import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.rest.gclient.ReferenceClientParam; import ca.uhn.fhir.rest.gclient.TokenClientParam; import ca.uhn.fhir.rest.server.IBundleProvider; @@ -78,7 +81,7 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless(); Patient pat2 = new Patient(); - pat.setGender(AdministrativeGender.FEMALE); + pat2.setGender(AdministrativeGender.FEMALE); IIdType patId2 = myPatientDao.create(pat2, mySrd).getId().toUnqualifiedVersionless(); SearchParameterMap map; @@ -97,6 +100,39 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv } + + @Test + public void testCreatingParamMarksCorrectResourcesForReindexing() { + Patient pat = new Patient(); + pat.setGender(AdministrativeGender.MALE); + IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs2 = new Observation(); + obs2.setStatus(ObservationStatus.FINAL); + IIdType obsId = myObservationDao.create(obs2, mySrd).getId().toUnqualifiedVersionless(); + + ResourceTable res = myResourceTableDao.findOne(patId.getIdPartAsLong()); + assertEquals(BaseHapiFhirDao.INDEX_STATUS_INDEXED, res.getIndexStatus().longValue()); + res = myResourceTableDao.findOne(obsId.getIdPartAsLong()); + assertEquals(BaseHapiFhirDao.INDEX_STATUS_INDEXED, res.getIndexStatus().longValue()); + + SearchParameter fooSp = new SearchParameter(); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("FOO SP"); + fooSp.setXpath("Patient.gender"); + fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(fooSp, mySrd); + + res = myResourceTableDao.findOne(patId.getIdPartAsLong()); + assertEquals(null, res.getIndexStatus()); + res = myResourceTableDao.findOne(obsId.getIdPartAsLong()); + assertEquals(BaseHapiFhirDao.INDEX_STATUS_INDEXED, res.getIndexStatus().longValue()); + + } + + @SuppressWarnings("unused") @Test public void testSearchQualifiedWithCustomReferenceParam() { From c85c2269a190bcd243b45d1e963266e755b18557 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Sat, 4 Feb 2017 14:41:11 -0500 Subject: [PATCH 6/7] Work on custom params --- .../ca/uhn/fhir/context/ModelScanner.java | 2 +- .../uhn/fhir/context/RuntimeSearchParam.java | 19 +++++- .../fhir/jpa/dao/BaseSearchParamRegistry.java | 12 ++++ .../fhir/jpa/dao/ISearchParamRegistry.java | 3 + .../dstu3/JpaConformanceProviderDstu3.java | 59 ++++++++++++++---- .../dstu3/SearchParamExtractorDstu3Test.java | 6 ++ ...rceProviderCustomSearchParamDstu3Test.java | 60 +++++++++++++++++-- .../fhir/rest/server/ValidateDstu2_1Test.java | 5 +- 8 files changed, 145 insertions(+), 21 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java index 96a418a7c95..e1d96461213 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java @@ -458,7 +458,7 @@ class ModelScanner { compositeOf.add(param); } - RuntimeSearchParam param = new RuntimeSearchParam(searchParam.name(), searchParam.description(), searchParam.path(), RestSearchParameterTypeEnum.COMPOSITE, compositeOf, null, toTargetList(searchParam.target()), RuntimeSearchParamStatusEnum.ACTIVE); + RuntimeSearchParam param = new RuntimeSearchParam(null, null, searchParam.name(), searchParam.description(), searchParam.path(), RestSearchParameterTypeEnum.COMPOSITE, compositeOf, null, toTargetList(searchParam.target()), RuntimeSearchParamStatusEnum.ACTIVE); theResourceDef.addSearchParam(param); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java index 7851ef55d6b..28c7377674a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java @@ -6,6 +6,8 @@ import java.util.List; import java.util.Set; import java.util.StringTokenizer; +import org.hl7.fhir.instance.model.api.IIdType; + import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum; /* @@ -29,7 +31,7 @@ import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum; */ public class RuntimeSearchParam { - + private final IIdType myId; private final List myCompositeOf; private final String myDescription; private final String myName; @@ -38,10 +40,21 @@ public class RuntimeSearchParam { private final Set myTargets; private final Set myProvidesMembershipInCompartments; private final RuntimeSearchParamStatusEnum myStatus; + private final String myUri; - public RuntimeSearchParam(String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, List theCompositeOf, + public IIdType getId() { + return myId; + } + + public String getUri() { + return myUri; + } + + public RuntimeSearchParam(IIdType theId, String theUri, String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, List theCompositeOf, Set theProvidesMembershipInCompartments, Set theTargets, RuntimeSearchParamStatusEnum theStatus) { super(); + myId = theId; + myUri = theUri; myName = theName; myDescription = theDescription; myPath = thePath; @@ -69,7 +82,7 @@ public class RuntimeSearchParam { } public RuntimeSearchParam(String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, Set theProvidesMembershipInCompartments, Set theTargets, RuntimeSearchParamStatusEnum theStatus) { - this(theName, theDescription, thePath, theParamType, null, theProvidesMembershipInCompartments, theTargets, theStatus); + this(null, null, theName, theDescription, thePath, theParamType, null, theProvidesMembershipInCompartments, theTargets, theStatus); } public List getCompositeOf() { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamRegistry.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamRegistry.java index 721b129e596..97c6c52cd86 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamRegistry.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamRegistry.java @@ -7,6 +7,7 @@ import java.util.Map; import javax.annotation.PostConstruct; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.Validate; import org.springframework.beans.factory.annotation.Autowired; @@ -16,6 +17,8 @@ import ca.uhn.fhir.context.RuntimeSearchParam; public abstract class BaseSearchParamRegistry implements ISearchParamRegistry { + private static final Map EMPTY_SP_MAP = Collections.emptyMap(); + private Map> myBuiltInSearchParams; @Autowired @@ -62,4 +65,13 @@ public abstract class BaseSearchParamRegistry implements ISearchParamRegistry { myBuiltInSearchParams = Collections.unmodifiableMap(resourceNameToSearchParams); } + @Override + public Collection getAllSearchParams(String theResourceName) { + Validate.notBlank(theResourceName, "theResourceName must not be null or blank"); + + Map map = myBuiltInSearchParams.get(theResourceName); + map = ObjectUtils.defaultIfNull(map, EMPTY_SP_MAP); + return Collections.unmodifiableCollection(map.values()); + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchParamRegistry.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchParamRegistry.java index 540c7b60275..f6d3ba87289 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchParamRegistry.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchParamRegistry.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.dao; +import java.util.Collection; import java.util.Map; import ca.uhn.fhir.context.RuntimeSearchParam; @@ -8,6 +9,8 @@ public interface ISearchParamRegistry { Map getActiveSearchParams(String theResourceName); + Collection getAllSearchParams(String theResourceName); + void forceRefresh(); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java index 600b162d086..b58d32c493a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.jpa.provider.dstu3; +import java.util.Collection; + /* * #%L * HAPI FHIR JPA Server @@ -27,10 +29,13 @@ import javax.servlet.http.HttpServletRequest; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.CapabilityStatement.*; import org.hl7.fhir.dstu3.model.Enumerations.SearchParamType; +import org.springframework.beans.factory.annotation.Autowired; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.dao.ISearchParamRegistry; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.ExtensionConstants; @@ -63,6 +68,9 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se super.setCache(false); } + @Autowired + private ISearchParamRegistry mySearchParamRegistry; + @Override public CapabilityStatement getServerConformance(HttpServletRequest theRequest) { CapabilityStatement retVal = myCachedValue; @@ -89,19 +97,46 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se nextResource.addExtension(new Extension(ExtensionConstants.CONF_RESOURCE_COUNT, new DecimalType(count))); } - // Add chained params - for (CapabilityStatementRestResourceSearchParamComponent nextParam : nextResource.getSearchParam()) { - if (nextParam.getType() == SearchParamType.REFERENCE) { -// List targets = nextParam.getTarget(); -// for (CodeType next : targets) { -// RuntimeResourceDefinition def = ctx.getResourceDefinition(next.getValue()); -// for (RuntimeSearchParam nextChainedParam : def.getSearchParams()) { -// nextParam.addChain(nextChainedParam.getName()); -// } -// } - } - } + nextResource.getSearchParam().clear(); + Collection searchParams = mySearchParamRegistry.getAllSearchParams(nextResource.getType()); + for (RuntimeSearchParam runtimeSp : searchParams) { + CapabilityStatementRestResourceSearchParamComponent confSp = nextResource.addSearchParam(); + confSp.setName(runtimeSp.getName()); + confSp.setDocumentation(runtimeSp.getDescription()); + confSp.setDefinition(runtimeSp.getUri()); + switch (runtimeSp.getParamType()) { + case COMPOSITE: + confSp.setType(SearchParamType.COMPOSITE); + break; + case DATE: + confSp.setType(SearchParamType.DATE); + break; + case NUMBER: + confSp.setType(SearchParamType.NUMBER); + break; + case QUANTITY: + confSp.setType(SearchParamType.QUANTITY); + break; + case REFERENCE: + confSp.setType(SearchParamType.REFERENCE); + break; + case STRING: + confSp.setType(SearchParamType.STRING); + break; + case TOKEN: + confSp.setType(SearchParamType.TOKEN); + break; + case URI: + confSp.setType(SearchParamType.URI); + break; + case HAS: + // Shouldn't happen + break; + } + + } + } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/SearchParamExtractorDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/SearchParamExtractorDstu3Test.java index 0de138ed407..a15ff03eede 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/SearchParamExtractorDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/SearchParamExtractorDstu3Test.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.dao.dstu3; import static org.junit.Assert.assertEquals; +import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -57,6 +58,11 @@ public class SearchParamExtractorDstu3Test { public void forceRefresh() { // nothing } + + @Override + public Collection getAllSearchParams(String theResourceName) { + throw new UnsupportedOperationException(); + } }; SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(ourCtx, ourValidationSupport, searchParamRegistry); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java index 695af8eda4a..ca2c680b359 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java @@ -6,14 +6,16 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import java.io.IOException; +import java.util.HashMap; import java.util.List; +import java.util.Map; -import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestComponent; +import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestResourceComponent; +import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent; import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; -import org.hl7.fhir.dstu3.model.Observation; import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; -import org.hl7.fhir.dstu3.model.Patient; -import org.hl7.fhir.dstu3.model.SearchParameter; import org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.After; @@ -61,6 +63,56 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv } } + @SuppressWarnings("unused") + @Test + public void testConformance() { + + // Add a custom search parameter + SearchParameter fooSp = new SearchParameter(); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("FOO SP"); + fooSp.setXpath("Patient.gender"); + fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(fooSp, mySrd); + + // Disable an existing parameter + fooSp = new SearchParameter(); + fooSp.setCode("gender"); + fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("Gender"); + fooSp.setXpath("Patient.gender"); + fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.RETIRED); + mySearchParameterDao.create(fooSp, mySrd); + + CapabilityStatement conformance = ourClient + .fetchConformance() + .ofType(CapabilityStatement.class) + .execute(); + + Map map = extractSearchParams(conformance, "Patient"); + + CapabilityStatementRestResourceSearchParamComponent param = map.get("foo"); + assertEquals("foo", param.getName()); + } + + private Map extractSearchParams(CapabilityStatement conformance, String resType) { + Map map = new HashMap(); + for (CapabilityStatementRestComponent nextRest : conformance.getRest()) { + for (CapabilityStatementRestResourceComponent nextResource : nextRest.getResource()) { + if (!resType.equals(nextResource.getType())) { + continue; + } + for (CapabilityStatementRestResourceSearchParamComponent nextParam : nextResource.getSearchParam()) { + map.put(nextParam.getName(), nextParam); + } + } + } + return map; + } + @SuppressWarnings("unused") @Test public void testSearchWithCustomParam() { diff --git a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/server/ValidateDstu2_1Test.java b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/server/ValidateDstu2_1Test.java index 92ca9c0c5d8..8a541b8ada5 100644 --- a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/server/ValidateDstu2_1Test.java +++ b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/server/ValidateDstu2_1Test.java @@ -96,8 +96,11 @@ public class ValidateDstu2_1Test { params.addParameter().setName("resource").setResource(patient); params.addParameter().setName("mode").setValue(new CodeType(" ")); + + String encodedResource = ourCtx.newXmlParser().encodeResourceToString(params); + HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$validate"); - httpPost.setEntity(new StringEntity(ourCtx.newXmlParser().encodeResourceToString(params), ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + httpPost.setEntity(new StringEntity(encodedResource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); HttpResponse status = ourClient.execute(httpPost); String resp = IOUtils.toString(status.getEntity().getContent()); From 54ac780cd5c9ffe177b6b03d855e80a63817b729 Mon Sep 17 00:00:00 2001 From: James Date: Sat, 4 Feb 2017 16:02:00 -0500 Subject: [PATCH 7/7] Custom params works! --- .../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 173 +++++++++++++----- .../main/java/ca/uhn/fhir/jpa/dao/IDao.java | 4 + .../dao/dstu3/SearchParamRegistryDstu3.java | 23 ++- .../dstu3/JpaConformanceProviderDstu3.java | 9 +- ...rceProviderCustomSearchParamDstu3Test.java | 149 +++++++++++---- src/changes/changes.xml | 6 + 6 files changed, 271 insertions(+), 93 deletions(-) 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 f9d43602500..9965a51e88b 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 @@ -25,9 +25,24 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.io.UnsupportedEncodingException; import java.text.Normalizer; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; -import javax.persistence.*; +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import javax.persistence.PersistenceContext; +import javax.persistence.PersistenceContextType; +import javax.persistence.Tuple; +import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; @@ -43,22 +58,64 @@ import org.apache.http.client.utils.URLEncodedUtils; import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.dstu3.model.StringType; -import org.hl7.fhir.instance.model.api.*; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.instance.model.api.IBaseCoding; +import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; +import org.hl7.fhir.instance.model.api.IBaseReference; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IDomainResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.PlatformTransactionManager; import com.google.common.base.Charsets; import com.google.common.collect.ArrayListMultimap; -import ca.uhn.fhir.context.*; +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.RuntimeChildResourceDefinition; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; -import ca.uhn.fhir.jpa.entity.*; +import ca.uhn.fhir.jpa.entity.BaseHasResource; +import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam; +import ca.uhn.fhir.jpa.entity.BaseTag; +import ca.uhn.fhir.jpa.entity.ForcedId; +import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum; +import ca.uhn.fhir.jpa.entity.ResourceHistoryTable; +import ca.uhn.fhir.jpa.entity.ResourceHistoryTag; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamQuantity; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri; +import ca.uhn.fhir.jpa.entity.ResourceLink; +import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.jpa.entity.ResourceTag; +import ca.uhn.fhir.jpa.entity.Search; +import ca.uhn.fhir.jpa.entity.SearchTypeEnum; +import ca.uhn.fhir.jpa.entity.TagDefinition; +import ca.uhn.fhir.jpa.entity.TagTypeEnum; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.util.DeleteConflict; -import ca.uhn.fhir.model.api.*; +import ca.uhn.fhir.model.api.IQueryParameterAnd; +import ca.uhn.fhir.model.api.IQueryParameterType; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.api.Tag; +import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.base.composite.BaseCodingDt; import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; import ca.uhn.fhir.model.dstu.resource.BaseResource; @@ -73,10 +130,21 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.method.MethodUtil; import ca.uhn.fhir.rest.method.QualifiedParamList; import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum; -import ca.uhn.fhir.rest.param.*; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.StringAndListParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenAndListParam; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.param.UriAndListParam; +import ca.uhn.fhir.rest.param.UriParam; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IBundleProvider; -import ca.uhn.fhir.rest.server.exceptions.*; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; @@ -86,16 +154,17 @@ import ca.uhn.fhir.util.OperationOutcomeUtil; public abstract class BaseHapiFhirDao implements IDao { - private static final String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest"; public static final long INDEX_STATUS_INDEXED = Long.valueOf(1L); + public static final long INDEX_STATUS_INDEXING_FAILED = Long.valueOf(2L); public static final String NS_JPA_PROFILE = "https://github.com/jamesagnew/hapi-fhir/ns/jpa/profile"; public static final String OO_SEVERITY_ERROR = "error"; public static final String OO_SEVERITY_INFO = "information"; public static final String OO_SEVERITY_WARN = "warning"; - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirDao.class); private static final Map ourRetrievalContexts = new HashMap(); + + private static final String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest"; /** * These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(Map)} */ @@ -104,8 +173,8 @@ public abstract class BaseHapiFhirDao implements IDao { * These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(Map)} */ static final Map> RESOURCE_META_PARAMS; - public static final String UCUM_NS = "http://unitsofmeasure.org"; + static { Map> resourceMetaParams = new HashMap>(); Map>> resourceMetaAndParams = new HashMap>>(); @@ -122,7 +191,6 @@ public abstract class BaseHapiFhirDao implements IDao { RESOURCE_META_PARAMS = Collections.unmodifiableMap(resourceMetaParams); RESOURCE_META_AND_PARAMS = Collections.unmodifiableMap(resourceMetaAndParams); } - @Autowired(required = true) private DaoConfig myConfig; @@ -157,6 +225,12 @@ public abstract class BaseHapiFhirDao implements IDao { @Autowired private ISearchResultDao mySearchResultDao; + protected void clearRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) { + if (theRequestDetails != null) { + theRequestDetails.getUserData().remove(PROCESSING_SUB_REQUEST); + } + } + protected void createForcedIdIfNeeded(ResourceTable theEntity, IIdType theId) { if (theId.isEmpty() == false && theId.hasIdPart()) { if (isValidPid(theId)) { @@ -471,6 +545,17 @@ public abstract class BaseHapiFhirDao implements IDao { return (IFhirResourceDao) myResourceTypeToDao.get(theType); } + @Override + public RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName) { + Map params = mySearchParamRegistry.getActiveSearchParams(theResourceDef.getName()); + return params.get(theParamName); + } + + @Override + public Collection getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) { + return mySearchParamRegistry.getActiveSearchParams(theResourceDef.getName()).values(); + } + protected TagDefinition getTag(TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) { CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery cq = builder.createQuery(TagDefinition.class); @@ -587,6 +672,12 @@ public abstract class BaseHapiFhirDao implements IDao { theProvider.setSearchResultDao(mySearchResultDao); } + protected void markRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) { + if (theRequestDetails != null) { + theRequestDetails.getUserData().put(PROCESSING_SUB_REQUEST, Boolean.TRUE); + } + } + protected void notifyInterceptors(RestOperationTypeEnum theOperationType, ActionRequestDetails theRequestDetails) { if (theRequestDetails.getId() != null && theRequestDetails.getId().hasResourceType() && isNotBlank(theRequestDetails.getResourceType())) { if (theRequestDetails.getId().getResourceType().equals(theRequestDetails.getResourceType()) == false) { @@ -607,18 +698,6 @@ public abstract class BaseHapiFhirDao implements IDao { } } - protected void markRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) { - if (theRequestDetails != null) { - theRequestDetails.getUserData().put(PROCESSING_SUB_REQUEST, Boolean.TRUE); - } - } - - protected void clearRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) { - if (theRequestDetails != null) { - theRequestDetails.getUserData().remove(PROCESSING_SUB_REQUEST); - } - } - public String parseContentTextIntoWords(IBaseResource theResource) { StringBuilder retVal = new StringBuilder(); @SuppressWarnings("rawtypes") @@ -897,12 +976,6 @@ public abstract class BaseHapiFhirDao implements IDao { return ids; } - @Override - public RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName) { - Map params = mySearchParamRegistry.getActiveSearchParams(theResourceDef.getName()); - return params.get(theParamName); - } - @SuppressWarnings("unused") @CoverageIgnore public BaseHasResource readEntity(IIdType theValueId) { @@ -1627,26 +1700,6 @@ public abstract class BaseHapiFhirDao implements IDao { } } - protected static List translateMatchUrl(String theMatchUrl) { - List parameters; - String matchUrl = theMatchUrl; - int questionMarkIndex = matchUrl.indexOf('?'); - if (questionMarkIndex != -1) { - matchUrl = matchUrl.substring(questionMarkIndex + 1); - } - matchUrl = matchUrl.replace("|", "%7C"); - matchUrl = matchUrl.replace("=>=", "=%3E%3D"); - matchUrl = matchUrl.replace("=<=", "=%3C%3D"); - matchUrl = matchUrl.replace("=>", "=%3E"); - matchUrl = matchUrl.replace("=<", "=%3C"); - if (matchUrl.contains(" ")) { - throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - URL is invalid (must not contain spaces)"); - } - - parameters = URLEncodedUtils.parse((matchUrl), Constants.CHARSET_UTF8, '&'); - return parameters; - } - public static SearchParameterMap translateMatchUrl(IDao theCallingDao, FhirContext theContext, String theMatchUrl, RuntimeResourceDefinition resourceDef) { SearchParameterMap paramMap = new SearchParameterMap(); List parameters = translateMatchUrl(theMatchUrl); @@ -1729,6 +1782,26 @@ public abstract class BaseHapiFhirDao implements IDao { return paramMap; } + protected static List translateMatchUrl(String theMatchUrl) { + List parameters; + String matchUrl = theMatchUrl; + int questionMarkIndex = matchUrl.indexOf('?'); + if (questionMarkIndex != -1) { + matchUrl = matchUrl.substring(questionMarkIndex + 1); + } + matchUrl = matchUrl.replace("|", "%7C"); + matchUrl = matchUrl.replace("=>=", "=%3E%3D"); + matchUrl = matchUrl.replace("=<=", "=%3C%3D"); + matchUrl = matchUrl.replace("=>", "=%3E"); + matchUrl = matchUrl.replace("=<", "=%3C"); + if (matchUrl.contains(" ")) { + throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - URL is invalid (must not contain spaces)"); + } + + parameters = URLEncodedUtils.parse((matchUrl), Constants.CHARSET_UTF8, '&'); + return parameters; + } + public static void validateResourceType(BaseHasResource theEntity, String theResourceName) { if (!theResourceName.equals(theEntity.getResourceType())) { throw new ResourceNotFoundException( diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IDao.java index ae92e971c4d..098b090a123 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IDao.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.dao; +import java.util.Collection; import java.util.Set; import org.hl7.fhir.instance.model.api.IAnyResource; @@ -77,4 +78,7 @@ public interface IDao { void populateFullTextFields(IBaseResource theResource, ResourceTable theEntity); RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName); + + Collection getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef); + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/SearchParamRegistryDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/SearchParamRegistryDstu3.java index 75123b7fa8b..7d36cd25dee 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/SearchParamRegistryDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/SearchParamRegistryDstu3.java @@ -71,7 +71,7 @@ public class SearchParamRegistryDstu3 extends BaseSearchParamRegistry { if (runtimeSp == null) { continue; } - + int dotIdx = runtimeSp.getPath().indexOf('.'); if (dotIdx == -1) { ourLog.warn("Can not determine resource type of {}", runtimeSp.getPath()); @@ -89,11 +89,22 @@ public class SearchParamRegistryDstu3 extends BaseSearchParamRegistry { Map> activeSearchParams = new HashMap>(); for (Entry> nextEntry : searchParams.entrySet()) { for (RuntimeSearchParam nextSp : nextEntry.getValue().values()) { - if (nextSp.getStatus() == RuntimeSearchParamStatusEnum.ACTIVE) { - if (!activeSearchParams.containsKey(nextEntry.getKey())) { - activeSearchParams.put(nextEntry.getKey(), new HashMap()); - } - activeSearchParams.get(nextEntry.getKey()).put(nextSp.getName(), nextSp); + String nextName = nextSp.getName(); + if (nextSp.getStatus() != RuntimeSearchParamStatusEnum.ACTIVE) { + nextSp = null; + } + + if (!activeSearchParams.containsKey(nextEntry.getKey())) { + activeSearchParams.put(nextEntry.getKey(), new HashMap()); + } + if (activeSearchParams.containsKey(nextEntry.getKey())) { + ourLog.info("Replacing existing/built in search param {}:{} with new one", nextEntry.getKey(), nextName); + } + + if (nextSp != null) { + activeSearchParams.get(nextEntry.getKey()).put(nextName, nextSp); + } else { + activeSearchParams.get(nextEntry.getKey()).remove(nextName); } } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java index b58d32c493a..b55b70e4e08 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java @@ -31,7 +31,9 @@ import org.hl7.fhir.dstu3.model.CapabilityStatement.*; import org.hl7.fhir.dstu3.model.Enumerations.SearchParamType; import org.springframework.beans.factory.annotation.Autowired; +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; @@ -67,9 +69,6 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se myDaoConfig = theDaoConfig; super.setCache(false); } - - @Autowired - private ISearchParamRegistry mySearchParamRegistry; @Override public CapabilityStatement getServerConformance(HttpServletRequest theRequest) { @@ -98,7 +97,9 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se } nextResource.getSearchParam().clear(); - Collection searchParams = mySearchParamRegistry.getAllSearchParams(nextResource.getType()); + String resourceName = nextResource.getType(); + RuntimeResourceDefinition resourceDef = myRestfulServer.getFhirContext().getResourceDefinition(resourceName); + Collection searchParams = mySystemDao.getSearchParamsByResourceType(resourceDef); for (RuntimeSearchParam runtimeSp : searchParams) { CapabilityStatementRestResourceSearchParamComponent confSp = nextResource.addSearchParam(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java index ca2c680b359..86d6e3df282 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderCustomSearchParamDstu3Test.java @@ -2,6 +2,8 @@ package ca.uhn.fhir.jpa.provider.dstu3; import static org.hamcrest.Matchers.contains; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; @@ -10,16 +12,21 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.CapabilityStatement; import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestComponent; import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestResourceComponent; import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent; import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; +import org.hl7.fhir.dstu3.model.Observation; import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.SearchParameter; import org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.After; import org.junit.AfterClass; +import org.junit.Before; import org.junit.Test; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; @@ -29,7 +36,7 @@ import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.rest.gclient.ReferenceClientParam; import ca.uhn.fhir.rest.gclient.TokenClientParam; import ca.uhn.fhir.rest.server.IBundleProvider; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProviderDstu3Test { @@ -58,14 +65,35 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv try { ourClient.create().resource(sp).execute(); fail(); - } catch (InvalidRequestException e) { - assertEquals("HTTP 400 Bad Request: Resource.status is missing or invalid: null", e.getMessage()); + } catch (UnprocessableEntityException e) { + assertEquals("HTTP 422 Unprocessable Entity: Resource.status is missing or invalid: null", e.getMessage()); } } - @SuppressWarnings("unused") + @Override + @Before + public void beforeResetConfig() { + super.beforeResetConfig(); + + myDaoConfig.setDefaultSearchParamsCanBeOverridden(new DaoConfig().isDefaultSearchParamsCanBeOverridden()); + mySearchParamRegsitry.forceRefresh(); + } + @Test - public void testConformance() { + public void testConformanceOverrideAllowed() { + myDaoConfig.setDefaultSearchParamsCanBeOverridden(true); + + CapabilityStatement conformance = ourClient + .fetchConformance() + .ofType(CapabilityStatement.class) + .execute(); + Map map = extractSearchParams(conformance, "Patient"); + + CapabilityStatementRestResourceSearchParamComponent param = map.get("foo"); + assertNull(param); + + param = map.get("gender"); + assertNotNull(param); // Add a custom search parameter SearchParameter fooSp = new SearchParameter(); @@ -76,7 +104,7 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL); fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE); mySearchParameterDao.create(fooSp, mySrd); - + // Disable an existing parameter fooSp = new SearchParameter(); fooSp.setCode("gender"); @@ -87,15 +115,72 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.RETIRED); mySearchParameterDao.create(fooSp, mySrd); - CapabilityStatement conformance = ourClient - .fetchConformance() - .ofType(CapabilityStatement.class) - .execute(); + mySearchParamRegsitry.forceRefresh(); - Map map = extractSearchParams(conformance, "Patient"); - - CapabilityStatementRestResourceSearchParamComponent param = map.get("foo"); + conformance = ourClient + .fetchConformance() + .ofType(CapabilityStatement.class) + .execute(); + map = extractSearchParams(conformance, "Patient"); + + param = map.get("foo"); assertEquals("foo", param.getName()); + + param = map.get("gender"); + assertNull(param); + + } + + @Test + public void testConformanceOverrideNotAllowed() { + myDaoConfig.setDefaultSearchParamsCanBeOverridden(false); + + CapabilityStatement conformance = ourClient + .fetchConformance() + .ofType(CapabilityStatement.class) + .execute(); + Map map = extractSearchParams(conformance, "Patient"); + + CapabilityStatementRestResourceSearchParamComponent param = map.get("foo"); + assertNull(param); + + param = map.get("gender"); + assertNotNull(param); + + // Add a custom search parameter + SearchParameter fooSp = new SearchParameter(); + fooSp.setCode("foo"); + fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("FOO SP"); + fooSp.setXpath("Patient.gender"); + fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE); + mySearchParameterDao.create(fooSp, mySrd); + + // Disable an existing parameter + fooSp = new SearchParameter(); + fooSp.setCode("gender"); + fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN); + fooSp.setTitle("Gender"); + fooSp.setXpath("Patient.gender"); + fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL); + fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.RETIRED); + mySearchParameterDao.create(fooSp, mySrd); + + mySearchParamRegsitry.forceRefresh(); + + conformance = ourClient + .fetchConformance() + .ofType(CapabilityStatement.class) + .execute(); + map = extractSearchParams(conformance, "Patient"); + + param = map.get("foo"); + assertEquals("foo", param.getName()); + + param = map.get("gender"); + assertNotNull(param); + } private Map extractSearchParams(CapabilityStatement conformance, String resType) { @@ -112,7 +197,7 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv } return map; } - + @SuppressWarnings("unused") @Test public void testSearchWithCustomParam() { @@ -125,9 +210,9 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL); fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE); mySearchParameterDao.create(fooSp, mySrd); - + mySearchParamRegsitry.forceRefresh(); - + Patient pat = new Patient(); pat.setGender(AdministrativeGender.MALE); IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless(); @@ -142,17 +227,16 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv Bundle result; result = ourClient - .search() - .forResource(Patient.class) - .where(new TokenClientParam("foo").exactly().code("male")) - .returnBundle(Bundle.class) - .execute(); + .search() + .forResource(Patient.class) + .where(new TokenClientParam("foo").exactly().code("male")) + .returnBundle(Bundle.class) + .execute(); foundResources = toUnqualifiedVersionlessIdValues(result); assertThat(foundResources, contains(patId.getValue())); } - @Test public void testCreatingParamMarksCorrectResourcesForReindexing() { Patient pat = new Patient(); @@ -167,7 +251,7 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv assertEquals(BaseHapiFhirDao.INDEX_STATUS_INDEXED, res.getIndexStatus().longValue()); res = myResourceTableDao.findOne(obsId.getIdPartAsLong()); assertEquals(BaseHapiFhirDao.INDEX_STATUS_INDEXED, res.getIndexStatus().longValue()); - + SearchParameter fooSp = new SearchParameter(); fooSp.setCode("foo"); fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN); @@ -184,7 +268,6 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv } - @SuppressWarnings("unused") @Test public void testSearchQualifiedWithCustomReferenceParam() { @@ -197,9 +280,9 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL); fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE); mySearchParameterDao.create(fooSp, mySrd); - + mySearchParamRegsitry.forceRefresh(); - + Patient pat = new Patient(); pat.setGender(AdministrativeGender.MALE); IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless(); @@ -218,16 +301,16 @@ public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProv Bundle result; result = ourClient - .search() - .forResource(Observation.class) - .where(new ReferenceClientParam("foo").hasChainedProperty(Patient.GENDER.exactly().code("male"))) - .returnBundle(Bundle.class) - .execute(); + .search() + .forResource(Observation.class) + .where(new ReferenceClientParam("foo").hasChainedProperty(Patient.GENDER.exactly().code("male"))) + .returnBundle(Bundle.class) + .execute(); foundResources = toUnqualifiedVersionlessIdValues(result); assertThat(foundResources, contains(obsId1.getValue())); } - + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 58fb44044d8..4c12c5bc397 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -23,6 +23,12 @@ ]]> + + The JPA server now supports custom search parameters in DSTU3 + mode. This allows users to create search parameters which contain + custom paths, or even override and disable existing search + parameters. + Fix issue in AuthorizationIntetceptor where transactions are blocked even when they