From b4a362b8eeca42fc96fd68c5b6f48ea98d1403db Mon Sep 17 00:00:00 2001 From: James Date: Wed, 1 Feb 2017 05:55:35 -0500 Subject: [PATCH] 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