Merge branch 'custom_jpa_search_params'

This commit is contained in:
James 2017-02-04 16:34:05 -05:00
commit 7149d2e053
58 changed files with 2323 additions and 531 deletions

8
HELPWANTED.md Normal file
View File

@ -0,0 +1,8 @@
# Help Wanted
This page is a work in progress!
It serves as a place to list potential help a new volunteer could offer.
* Investigate adding support for FHIR's RDF (Turtle) encoding to HAPI

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>hapi-fhir-android-realm</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@ -82,87 +82,22 @@ public class FhirContext {
private Map<String, Class<? extends IBaseResource>> myDefaultTypeForProfile = new HashMap<String, Class<? extends IBaseResource>>(); private Map<String, Class<? extends IBaseResource>> myDefaultTypeForProfile = new HashMap<String, Class<? extends IBaseResource>>();
private volatile Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = Collections.emptyMap(); private volatile Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = Collections.emptyMap();
private boolean myInitialized; private boolean myInitialized;
private boolean myInitializing;
private HapiLocalizer myLocalizer = new HapiLocalizer(); private HapiLocalizer myLocalizer = new HapiLocalizer();
private volatile Map<String, BaseRuntimeElementDefinition<?>> myNameToElementDefinition = Collections.emptyMap(); private volatile Map<String, BaseRuntimeElementDefinition<?>> myNameToElementDefinition = Collections.emptyMap();
private volatile Map<String, RuntimeResourceDefinition> myNameToResourceDefinition = Collections.emptyMap(); private volatile Map<String, RuntimeResourceDefinition> myNameToResourceDefinition = Collections.emptyMap();
private volatile Map<String, Class<? extends IBaseResource>> myNameToResourceType; private volatile Map<String, Class<? extends IBaseResource>> myNameToResourceType;
private volatile INarrativeGenerator myNarrativeGenerator; private volatile INarrativeGenerator myNarrativeGenerator;
private volatile IParserErrorHandler myParserErrorHandler = new LenientErrorHandler(); private volatile IParserErrorHandler myParserErrorHandler = new LenientErrorHandler();
private ParserOptions myParserOptions = new ParserOptions();
private Set<PerformanceOptionsEnum> myPerformanceOptions = new HashSet<PerformanceOptionsEnum>(); private Set<PerformanceOptionsEnum> myPerformanceOptions = new HashSet<PerformanceOptionsEnum>();
private Collection<Class<? extends IBaseResource>> myResourceTypesToScan; private Collection<Class<? extends IBaseResource>> myResourceTypesToScan;
private volatile IRestfulClientFactory myRestfulClientFactory; private volatile IRestfulClientFactory myRestfulClientFactory;
private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition; private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
private final IFhirVersion myVersion;
private Map<FhirVersionEnum, Map<String, Class<? extends IBaseResource>>> myVersionToNameToResourceType = Collections.emptyMap();
private boolean myInitializing;
private IContextValidationSupport<?, ?, ?, ?, ?, ?> myValidationSupport; private IContextValidationSupport<?, ?, ?, ?, ?, ?> myValidationSupport;
private final IFhirVersion myVersion;
/** private Map<FhirVersionEnum, Map<String, Class<? extends IBaseResource>>> myVersionToNameToResourceType = Collections.emptyMap();
* 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.
* <p>
* In other words, call {@link #setValidationSupport(IContextValidationSupport)} before
* calling {@link #newFluentPath()}
* </p>
* <p>
* 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}
* </p>
*
* @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 <code>null</code>
*/
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 <code>null</code>
*/
public void setParserOptions(ParserOptions theParserOptions) {
Validate.notNull(theParserOptions, "theParserOptions must not be null");
myParserOptions = theParserOptions;
}
/** /**
* @deprecated It is recommended that you use one of the static initializer methods instead * @deprecated It is recommended that you use one of the static initializer methods instead
@ -172,7 +107,7 @@ public class FhirContext {
public FhirContext() { public FhirContext() {
this(EMPTY_LIST); this(EMPTY_LIST);
} }
/** /**
* @deprecated It is recommended that you use one of the static initializer methods instead * @deprecated It is recommended that you use one of the static initializer methods instead
* of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()}
@ -282,7 +217,7 @@ public class FhirContext {
validateInitialized(); validateInitialized();
return myNameToResourceDefinition.values(); return myNameToResourceDefinition.values();
} }
/** /**
* Returns the default resource type for the given profile * Returns the default resource type for the given profile
* *
@ -332,7 +267,7 @@ public class FhirContext {
validateInitialized(); validateInitialized();
return Collections.unmodifiableCollection(myClassToElementDefinition.values()); 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 * This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with
* caution * caution
@ -348,6 +283,16 @@ public class FhirContext {
return myNarrativeGenerator; 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 <code>null</code>
*/
public ParserOptions getParserOptions() {
return myParserOptions;
}
/** /**
* Get the configured performance options * Get the configured performance options
*/ */
@ -449,6 +394,20 @@ public class FhirContext {
return myIdToResourceDefinition.get(theId); return myIdToResourceDefinition.get(theId);
} }
// /**
// * Return an unmodifiable collection containing all known resource definitions
// */
// public Collection<RuntimeResourceDefinition> getResourceDefinitions() {
//
// Set<Class<? extends IBase>> datatypes = Collections.emptySet();
// Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> existing = Collections.emptyMap();
// HashMap<String, Class<? extends IBaseResource>> types = new HashMap<String, Class<? extends IBaseResource>>();
// ModelScanner.scanVersionPropertyFile(datatypes, types, myVersion.getVersion(), existing);
// for (int next : types.)
//
// return Collections.unmodifiableCollection(myIdToResourceDefinition.values());
// }
/** /**
* Returns the scanned runtime models. This is an advanced feature which is generally only needed for extending the * Returns the scanned runtime models. This is an advanced feature which is generally only needed for extending the
* core library. * core library.
@ -476,6 +435,19 @@ public class FhirContext {
return myRuntimeChildUndeclaredExtensionDefinition; 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() { public IFhirVersion getVersion() {
return myVersion; return myVersion;
} }
@ -500,6 +472,28 @@ public class FhirContext {
return myVersion.newBundleFactory(this); 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.
* <p>
* In other words, call {@link #setValidationSupport(IContextValidationSupport)} before
* calling {@link #newFluentPath()}
* </p>
* <p>
* 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}
* </p>
*
* @since 2.2
*/
public IFluentPath newFluentPath() {
return myVersion.createFluentPathExecutor(this);
}
/** /**
* Create and return a new JSON parser. * Create and return a new JSON parser.
* *
@ -783,6 +777,17 @@ public class FhirContext {
myParserErrorHandler = theParserErrorHandler; myParserErrorHandler = theParserErrorHandler;
} }
/**
* Sets the parser options object which will be used to supply default
* options to newly created parsers
*
* @param theParserOptions The parser options object - Must not be <code>null</code>
*/
public void setParserOptions(ParserOptions theParserOptions) {
Validate.notNull(theParserOptions, "theParserOptions must not be null");
myParserOptions = theParserOptions;
}
/** /**
* Sets the configured performance options * Sets the configured performance options
* *
@ -818,6 +823,15 @@ public class FhirContext {
this.myRestfulClientFactory = theRestfulClientFactory; this.myRestfulClientFactory = theRestfulClientFactory;
} }
/**
* Sets the validation support module to use for this context. The validation support module
* is used to supply underlying infrastructure such as conformance resources (StructureDefinition, ValueSet, etc)
* as well as to provide terminology services to modules such as the validator and FluentPath executor
*/
public void setValidationSupport(IContextValidationSupport<?, ?, ?, ?, ?, ?> theValidationSupport) {
myValidationSupport = theValidationSupport;
}
@SuppressWarnings({ "cast" }) @SuppressWarnings({ "cast" })
private List<Class<? extends IElement>> toElementList(Collection<Class<? extends IBaseResource>> theResourceTypes) { private List<Class<? extends IElement>> toElementList(Collection<Class<? extends IBaseResource>> theResourceTypes) {
if (theResourceTypes == null) { if (theResourceTypes == null) {
@ -850,6 +864,13 @@ public class FhirContext {
return new FhirContext(FhirVersionEnum.DSTU2); 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 * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2_HL7ORG DSTU2} (using the Reference
* Implementation Structures) * Implementation Structures)
@ -885,11 +906,4 @@ public class FhirContext {
return retVal; 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);
}
} }

View File

@ -34,6 +34,7 @@ import java.util.Map.Entry;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.api.*; 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.ExtensionDt;
import ca.uhn.fhir.model.api.IDatatype; import ca.uhn.fhir.model.api.IDatatype;
import ca.uhn.fhir.model.api.IElement; 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); theResourceDef.addSearchParam(param);
nameToParam.put(param.getName(), param); nameToParam.put(param.getName(), param);
} }
@ -457,7 +458,7 @@ class ModelScanner {
compositeOf.add(param); compositeOf.add(param);
} }
RuntimeSearchParam param = new RuntimeSearchParam(searchParam.name(), searchParam.description(), searchParam.path(), RestSearchParameterTypeEnum.COMPOSITE, compositeOf, null, toTargetList(searchParam.target())); RuntimeSearchParam param = new RuntimeSearchParam(null, null, searchParam.name(), searchParam.description(), searchParam.path(), RestSearchParameterTypeEnum.COMPOSITE, compositeOf, null, toTargetList(searchParam.target()), RuntimeSearchParamStatusEnum.ACTIVE);
theResourceDef.addSearchParam(param); theResourceDef.addSearchParam(param);
} }
} }

View File

@ -6,6 +6,8 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum;
/* /*
@ -29,7 +31,7 @@ import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum;
*/ */
public class RuntimeSearchParam { public class RuntimeSearchParam {
private final IIdType myId;
private final List<RuntimeSearchParam> myCompositeOf; private final List<RuntimeSearchParam> myCompositeOf;
private final String myDescription; private final String myDescription;
private final String myName; private final String myName;
@ -37,15 +39,28 @@ public class RuntimeSearchParam {
private final String myPath; private final String myPath;
private final Set<String> myTargets; private final Set<String> myTargets;
private final Set<String> myProvidesMembershipInCompartments; private final Set<String> myProvidesMembershipInCompartments;
private final RuntimeSearchParamStatusEnum myStatus;
private final String myUri;
public RuntimeSearchParam(String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, List<RuntimeSearchParam> theCompositeOf, public IIdType getId() {
Set<String> theProvidesMembershipInCompartments, Set<String> theTargets) { return myId;
}
public String getUri() {
return myUri;
}
public RuntimeSearchParam(IIdType theId, String theUri, String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, List<RuntimeSearchParam> theCompositeOf,
Set<String> theProvidesMembershipInCompartments, Set<String> theTargets, RuntimeSearchParamStatusEnum theStatus) {
super(); super();
myId = theId;
myUri = theUri;
myName = theName; myName = theName;
myDescription = theDescription; myDescription = theDescription;
myPath = thePath; myPath = thePath;
myParamType = theParamType; myParamType = theParamType;
myCompositeOf = theCompositeOf; myCompositeOf = theCompositeOf;
myStatus = theStatus;
if (theProvidesMembershipInCompartments != null && !theProvidesMembershipInCompartments.isEmpty()) { if (theProvidesMembershipInCompartments != null && !theProvidesMembershipInCompartments.isEmpty()) {
myProvidesMembershipInCompartments = Collections.unmodifiableSet(theProvidesMembershipInCompartments); myProvidesMembershipInCompartments = Collections.unmodifiableSet(theProvidesMembershipInCompartments);
} else { } else {
@ -62,8 +77,12 @@ public class RuntimeSearchParam {
return myTargets; return myTargets;
} }
public RuntimeSearchParam(String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, Set<String> theProvidesMembershipInCompartments, Set<String> theTargets) { public RuntimeSearchParamStatusEnum getStatus() {
this(theName, theDescription, thePath, theParamType, null, theProvidesMembershipInCompartments, theTargets); return myStatus;
}
public RuntimeSearchParam(String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, Set<String> theProvidesMembershipInCompartments, Set<String> theTargets, RuntimeSearchParamStatusEnum theStatus) {
this(null, null, theName, theDescription, thePath, theParamType, null, theProvidesMembershipInCompartments, theTargets, theStatus);
} }
public List<RuntimeSearchParam> getCompositeOf() { public List<RuntimeSearchParam> getCompositeOf() {
@ -108,4 +127,10 @@ public class RuntimeSearchParam {
return myProvidesMembershipInCompartments; return myProvidesMembershipInCompartments;
} }
public enum RuntimeSearchParamStatusEnum {
ACTIVE,
DRAFT,
RETIRED
}
} }

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.rest.annotation; package ca.uhn.fhir.rest.annotation;
import java.lang.annotation.ElementType;
/* /*
* #%L * #%L
* HAPI FHIR - Core Library * 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 not use this file except in compliance with the License.
* You may obtain a copy of the License at * 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 * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * 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.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.CompositeParam; import ca.uhn.fhir.rest.param.CompositeParam;
import ca.uhn.fhir.rest.param.ReferenceParam; 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) @Retention(RetentionPolicy.RUNTIME)
@Target(value=ElementType.PARAMETER)
public @interface OptionalParam { public @interface OptionalParam {
public static final String ALLOW_CHAIN_ANY = "*"; public static final String ALLOW_CHAIN_ANY = "*";
@ -42,63 +44,63 @@ public @interface OptionalParam {
public static final String ALLOW_CHAIN_NOTCHAINED = ""; public static final String ALLOW_CHAIN_NOTCHAINED = "";
/** /**
* For reference parameters ({@link ReferenceParam}) this value may be * For reference parameters ({@link ReferenceParam}) this value may be
* used to indicate which chain values (if any) are <b>not</b> valid * used to indicate which chain values (if any) are <b>not</b> valid
* for the given parameter. Values here will supercede any values specified * for the given parameter. Values here will supercede any values specified
* in {@link #chainWhitelist()} * in {@link #chainWhitelist()}
* <p> * <p>
* If the parameter annotated with this annotation is not a {@link ReferenceParam}, * If the parameter annotated with this annotation is not a {@link ReferenceParam},
* this value must not be populated. * this value must not be populated.
* </p> * </p>
*/ */
String[] chainBlacklist() default {}; 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).
* <p>
* Valid values for this parameter include:
* </p>
* <ul>
* <li><code>chainWhitelist={ OptionalParam.ALLOW_CHAIN_NOTCHAINED }</code> - Only allow resource reference (no chaining allowed for this parameter)</li>
* <li><code>chainWhitelist={ OptionalParam.ALLOW_CHAIN_ANY }</code> - Allow any chaining at all (including a non chained value, <b>this is the default</b>)</li>
* <li><code>chainWhitelist={ "foo", "bar" }</code> - Allow property.foo and property.bar</li>
* </ul>
* <p>
* Any values specified in
* {@link #chainBlacklist()} will supercede (have priority over) values
* here.
* </p>
* <p>
* If the parameter annotated with this annotation is not a {@link ReferenceParam},
* this value must not be populated.
* </p>
*/
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.
* <p>
* If the parameter annotated with this annotation is not a {@link CompositeParam},
* this value must not be populated.
* </p>
*/
Class<? extends IQueryParameterType>[] 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).
* <p>
* Valid values for this parameter include:
* </p>
* <ul>
* <li><code>chainWhitelist={ OptionalParam.ALLOW_CHAIN_NOTCHAINED }</code> - Only allow resource reference (no chaining allowed for this parameter)</li>
* <li><code>chainWhitelist={ OptionalParam.ALLOW_CHAIN_ANY }</code> - Allow any chaining at all (including a non chained value, <b>this is the default</b>)</li>
* <li><code>chainWhitelist={ "foo", "bar" }</code> - Allow property.foo and property.bar</li>
* </ul>
* <p>
* Any values specified in
* {@link #chainBlacklist()} will supercede (have priority over) values
* here.
* </p>
* <p>
* If the parameter annotated with this annotation is not a {@link ReferenceParam},
* this value must not be populated.
* </p>
*/
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.
* <p>
* If the parameter annotated with this annotation is not a {@link CompositeParam},
* this value must not be populated.
* </p>
*/
Class<? extends IQueryParameterType>[] 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 * simple string (e.g. "name", or "identifier") which will be the name
* of the URL parameter used to populate this method parameter. * of the URL parameter used to populate this method parameter.
* <p> * <p>
* Most resource model classes have constants which may be used to * Most resource model classes have constants which may be used to
* supply values for this field, e.g. <code>Patient.SP_NAME</code> or * supply values for this field, e.g. <code>Patient.SP_NAME</code> or
* <code>Observation.SP_DATE</code> * <code>Observation.SP_DATE</code>
* </p> * </p>
* <p> * <p>
* If you wish to specify a parameter for a resource reference which * If you wish to specify a parameter for a resource reference which
* only accepts a specific chained value, it is also valid to supply * only accepts a specific chained value, it is also valid to supply
@ -109,13 +111,13 @@ public @interface OptionalParam {
*/ */
String name(); String name();
/** /**
* For resource reference parameters ({@link ReferenceParam}) this value may be * For resource reference parameters ({@link ReferenceParam}) this value may be
* used to indicate the resource type(s) which may be referenced by this param. * used to indicate the resource type(s) which may be referenced by this param.
* <p> * <p>
* If the parameter annotated with this annotation is not a {@link ReferenceParam}, * If the parameter annotated with this annotation is not a {@link ReferenceParam},
* this value must not be populated. * this value must not be populated.
* </p> * </p>
*/ */
Class<? extends IBaseResource>[] targetTypes() default {}; Class<? extends IBaseResource>[] targetTypes() default {};
} }

View File

@ -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.
* <p>
* Parameters with this annotation must be of type
* {@code Map<String, List<String>>}
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value=ElementType.PARAMETER)
public @interface RawParam {
// nothing
}

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.rest.annotation; package ca.uhn.fhir.rest.annotation;
import java.lang.annotation.ElementType;
/* /*
* #%L * #%L
* HAPI FHIR - Core Library * 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 not use this file except in compliance with the License.
* You may obtain a copy of the License at * 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 * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * 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.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.CompositeParam; import ca.uhn.fhir.rest.param.CompositeParam;
import ca.uhn.fhir.rest.param.ReferenceParam; 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 { public @interface RequiredParam {
/** /**
@ -45,32 +48,32 @@ public @interface RequiredParam {
*/ */
String[] chainBlacklist() default {}; String[] chainBlacklist() default {};
/** /**
* For reference parameters ({@link ReferenceParam}) this value may be * For reference parameters ({@link ReferenceParam}) this value may be
* used to indicate which chain values (if any) are valid for the given * 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) * 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} * If the list contains the value {@link OptionalParam#ALLOW_CHAIN_NOTCHAINED}
* then the reference param only supports the empty chain (i.e. the resource * then the reference param only supports the empty chain (i.e. the resource
* ID). * ID).
* <p> * <p>
* Valid values for this parameter include: * Valid values for this parameter include:
* </p> * </p>
* <ul> * <ul>
* <li><code>chainWhitelist={ OptionalParam.ALLOW_CHAIN_NOTCHAINED }</code> - Only allow resource reference (no chaining allowed for this parameter)</li> * <li><code>chainWhitelist={ OptionalParam.ALLOW_CHAIN_NOTCHAINED }</code> - Only allow resource reference (no chaining allowed for this parameter)</li>
* <li><code>chainWhitelist={ OptionalParam.ALLOW_CHAIN_ANY }</code> - Allow any chaining at all (including a non chained value, <b>this is the default</b>)</li> * <li><code>chainWhitelist={ OptionalParam.ALLOW_CHAIN_ANY }</code> - Allow any chaining at all (including a non chained value, <b>this is the default</b>)</li>
* <li><code>chainWhitelist={ "foo", "bar" }</code> - Allow property.foo and property.bar</li> * <li><code>chainWhitelist={ "foo", "bar" }</code> - Allow property.foo and property.bar</li>
* </ul> * </ul>
* <p> * <p>
* Any values specified in * Any values specified in
* {@link #chainBlacklist()} will supercede (have priority over) values * {@link #chainBlacklist()} will supercede (have priority over) values
* here. * here.
* </p> * </p>
* <p> * <p>
* If the parameter annotated with this annotation is not a {@link ReferenceParam}, * If the parameter annotated with this annotation is not a {@link ReferenceParam},
* this value must not be populated. * this value must not be populated.
* </p> * </p>
*/ */
String[] chainWhitelist() default {OptionalParam.ALLOW_CHAIN_ANY}; 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. * For composite parameters ({@link CompositeParam}) this parameter may be used to indicate the parameter type(s) which may be referenced by this param.

View File

@ -407,6 +407,8 @@ public class MethodUtil {
parameter.setType(theContext, parameterType, innerCollectionType, outerCollectionType); parameter.setType(theContext, parameterType, innerCollectionType, outerCollectionType);
MethodUtil.extractDescription(parameter, annotations); MethodUtil.extractDescription(parameter, annotations);
param = parameter; param = parameter;
} else if (nextAnnotation instanceof RawParam) {
param = new RawParamsParmeter(parameters);
} else if (nextAnnotation instanceof IncludeParam) { } else if (nextAnnotation instanceof IncludeParam) {
Class<? extends Collection<Include>> instantiableCollectionType; Class<? extends Collection<Include>> instantiableCollectionType;
Class<?> specType; Class<?> specType;

View File

@ -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<IParameter> myAllMethodParameters;
public RawParamsParmeter(List<IParameter> theParameters) {
myAllMethodParameters = theParameters;
}
@Override
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource)
throws InternalErrorException {
// not supported on client for now
}
@Override
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
HashMap<String, List<String>> 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<String, List<String>>();
}
retVal.put(nextName, Arrays.asList(theRequest.getParameters().get(nextName)));
}
}
return retVal;
}
@Override
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
Validate.isTrue(theParameterType.equals(Map.class), "Parameter with @" + RawParam.class + " must be of type Map<String, List<String>>");
}
}

View File

@ -130,11 +130,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
@Override @Override
public ReturnTypeEnum getReturnType() { public ReturnTypeEnum getReturnType() {
// if (getContext().getVersion().getVersion() == FhirVersionEnum.DSTU1) {
return ReturnTypeEnum.BUNDLE; return ReturnTypeEnum.BUNDLE;
// } else {
// return ReturnTypeEnum.RESOURCE;
// }
} }
@Override @Override
@ -406,14 +402,27 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
if (dotIdx < colonIdx) { if (dotIdx < colonIdx) {
retVal.setDotQualifier(theParamName.substring(dotIdx, colonIdx)); retVal.setDotQualifier(theParamName.substring(dotIdx, colonIdx));
retVal.setColonQualifier(theParamName.substring(colonIdx)); retVal.setColonQualifier(theParamName.substring(colonIdx));
retVal.setParamName(theParamName.substring(0, dotIdx));
retVal.setWholeQualifier(theParamName.substring(dotIdx));
} else { } else {
retVal.setColonQualifier(theParamName.substring(colonIdx, dotIdx)); retVal.setColonQualifier(theParamName.substring(colonIdx, dotIdx));
retVal.setDotQualifier(theParamName.substring(dotIdx)); retVal.setDotQualifier(theParamName.substring(dotIdx));
retVal.setParamName(theParamName.substring(0, colonIdx));
retVal.setWholeQualifier(theParamName.substring(colonIdx));
} }
} else if (dotIdx != -1) { } else if (dotIdx != -1) {
retVal.setDotQualifier(theParamName.substring(dotIdx)); retVal.setDotQualifier(theParamName.substring(dotIdx));
retVal.setParamName(theParamName.substring(0, dotIdx));
retVal.setWholeQualifier(theParamName.substring(dotIdx));
} else if (colonIdx != -1) { } else if (colonIdx != -1) {
retVal.setColonQualifier(theParamName.substring(colonIdx)); retVal.setColonQualifier(theParamName.substring(colonIdx));
retVal.setParamName(theParamName.substring(0, colonIdx));
retVal.setWholeQualifier(theParamName.substring(colonIdx));
} else {
retVal.setParamName(theParamName);
retVal.setColonQualifier(null);
retVal.setDotQualifier(null);
retVal.setWholeQualifier(null);
} }
return retVal; return retVal;
@ -423,6 +432,8 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
private String myColonQualifier; private String myColonQualifier;
private String myDotQualifier; private String myDotQualifier;
private String myParamName;
private String myWholeQualifier;
public boolean passes(Set<String> theQualifierWhitelist, Set<String> theQualifierBlacklist) { public boolean passes(Set<String> theQualifierWhitelist, Set<String> theQualifierBlacklist) {
if (theQualifierWhitelist != null) { if (theQualifierWhitelist != null) {
@ -468,6 +479,14 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
return true; return true;
} }
public void setParamName(String theParamName) {
myParamName = theParamName;
}
public String getParamName() {
return myParamName;
}
public void setColonQualifier(String theColonQualifier) { public void setColonQualifier(String theColonQualifier) {
myColonQualifier = theColonQualifier; myColonQualifier = theColonQualifier;
} }
@ -476,6 +495,14 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
myDotQualifier = theDotQualifier; myDotQualifier = theDotQualifier;
} }
public String getWholeQualifier() {
return myWholeQualifier;
}
public void setWholeQualifier(String theWholeQualifier) {
myWholeQualifier = theWholeQualifier;
}
} }
public static BaseHttpClientInvocation createSearchInvocation(FhirContext theContext, String theSearchUrl, Map<String, List<String>> theParams) { public static BaseHttpClientInvocation createSearchInvocation(FhirContext theContext, String theSearchUrl, Map<String, List<String>> theParams) {

View File

@ -43,11 +43,9 @@ ca.uhn.fhir.rest.param.ResourceParameter.noContentTypeInRequest=No Content-Type
ca.uhn.fhir.rest.param.ResourceParameter.failedToParseRequest=Failed to parse request body as {0} resource. Error was: {1} ca.uhn.fhir.rest.param.ResourceParameter.failedToParseRequest=Failed to parse request body as {0} resource. Error was: {1}
ca.uhn.fhir.parser.ParserState.wrongResourceTypeFound=Incorrect resource type found, expected "{0}" but found "{1}" ca.uhn.fhir.parser.ParserState.wrongResourceTypeFound=Incorrect resource type found, expected "{0}" but found "{1}"
ca.uhn.fhir.rest.server.RestfulServer.getPagesNonHttpGet=Requests for _getpages must use HTTP GET ca.uhn.fhir.rest.server.RestfulServer.getPagesNonHttpGet=Requests for _getpages must use HTTP GET
ca.uhn.fhir.rest.server.RestfulServer.unknownMethod=Invalid request: The FHIR endpoint on this server does not know how to handle {0} operation[{1}] with parameters [{2}] ca.uhn.fhir.rest.server.RestfulServer.unknownMethod=Invalid request: The FHIR endpoint on this server does not know how to handle {0} operation[{1}] with parameters [{2}]
ca.uhn.fhir.rest.server.RestfulServer.rootRequest=This is the base URL of FHIR server. Unable to handle this request, as it does not contain a resource type or operation name. ca.uhn.fhir.rest.server.RestfulServer.rootRequest=This is the base URL of FHIR server. Unable to handle this request, as it does not contain a resource type or operation name.
ca.uhn.fhir.validation.ValidationContext.unableToDetermineEncoding=Unable to determine encoding (e.g. XML / JSON) on validation input. Is this a valid FHIR resource body? ca.uhn.fhir.validation.ValidationContext.unableToDetermineEncoding=Unable to determine encoding (e.g. XML / JSON) on validation input. Is this a valid FHIR resource body?
ca.uhn.fhir.validation.FhirValidator.noPhlocWarningOnStartup=Phloc-schematron library not found on classpath, will not attempt to perform schematron validation ca.uhn.fhir.validation.FhirValidator.noPhlocWarningOnStartup=Phloc-schematron library not found on classpath, will not attempt to perform schematron validation
ca.uhn.fhir.validation.FhirValidator.noPhlocError=Phloc-schematron library not found on classpath, can not enable perform schematron validation ca.uhn.fhir.validation.FhirValidator.noPhlocError=Phloc-schematron library not found on classpath, can not enable perform schematron validation
@ -81,6 +79,7 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.unableToDeleteNotFound=Unable to fin
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulCreate=Successfully created resource "{0}" in {1}ms ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulCreate=Successfully created resource "{0}" in {1}ms
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulUpdate=Successfully updated resource "{0}" in {1}ms ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulUpdate=Successfully updated resource "{0}" in {1}ms
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulDeletes=Successfully deleted {0} resource(s) in {1}ms ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulDeletes=Successfully deleted {0} resource(s) in {1}ms
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidSearchParameter=Unknown search parameter "{0}". Value search parameters for this search are: {1}
ca.uhn.fhir.jpa.dao.SearchBuilder.invalidQuantityPrefix=Unable to handle quantity prefix "{0}" for value: {1} ca.uhn.fhir.jpa.dao.SearchBuilder.invalidQuantityPrefix=Unable to handle quantity prefix "{0}" for value: {1}
ca.uhn.fhir.jpa.dao.SearchBuilder.invalidNumberPrefix=Unable to handle number prefix "{0}" for value: {1} ca.uhn.fhir.jpa.dao.SearchBuilder.invalidNumberPrefix=Unable to handle number prefix "{0}" for value: {1}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -10,7 +10,7 @@ package ca.uhn.fhir.jpa.config;
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * 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 * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * 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 org.springframework.context.annotation.Primary;
import ca.uhn.fhir.context.FhirContext; 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.SearchParamExtractorDstu1;
import ca.uhn.fhir.jpa.dao.SearchParamRegistryDstu1;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu1; import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu1;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
@ -39,9 +41,10 @@ import ca.uhn.fhir.model.dstu2.composite.MetaDt;
public class BaseDstu1Config extends BaseConfig { public class BaseDstu1Config extends BaseConfig {
private static FhirContext ourFhirContextDstu1; private static FhirContext ourFhirContextDstu1;
@Bean(autowire = Autowire.BY_TYPE) @Bean
public IHapiTerminologySvc terminologyService() { @Primary
return new HapiTerminologySvcDstu1(); public FhirContext defaultFhirContext() {
return fhirContextDstu1();
} }
@Bean(name = "myFhirContextDstu1") @Bean(name = "myFhirContextDstu1")
@ -53,19 +56,22 @@ public class BaseDstu1Config extends BaseConfig {
return ourFhirContextDstu1; return ourFhirContextDstu1;
} }
@Bean
@Primary
public FhirContext defaultFhirContext() {
return fhirContextDstu1();
}
@Bean(name = "mySystemDaoDstu1", autowire = Autowire.BY_NAME) @Bean(name = "mySystemDaoDstu1", autowire = Autowire.BY_NAME)
public ca.uhn.fhir.jpa.dao.IFhirSystemDao<List<IResource>, MetaDt> fhirSystemDaoDstu1() { public ca.uhn.fhir.jpa.dao.IFhirSystemDao<List<IResource>, MetaDt> fhirSystemDaoDstu1() {
ca.uhn.fhir.jpa.dao.FhirSystemDaoDstu1 retVal = new ca.uhn.fhir.jpa.dao.FhirSystemDaoDstu1(); ca.uhn.fhir.jpa.dao.FhirSystemDaoDstu1 retVal = new ca.uhn.fhir.jpa.dao.FhirSystemDaoDstu1();
return retVal; return retVal;
} }
@Bean(autowire = Autowire.BY_TYPE)
public SearchParamExtractorDstu1 searchParamExtractor() {
return new SearchParamExtractorDstu1();
}
@Bean
public ISearchParamRegistry searchParamRegistry() {
return new SearchParamRegistryDstu1();
}
@Bean(name = "mySystemProviderDstu1") @Bean(name = "mySystemProviderDstu1")
public ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu1 systemDaoDstu1() { public ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu1 systemDaoDstu1() {
ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu1 retVal = new ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu1(); 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; return retVal;
} }
@Bean(autowire=Autowire.BY_TYPE) @Bean(autowire = Autowire.BY_TYPE)
public SearchParamExtractorDstu1 searchParamExtractor() { public IHapiTerminologySvc terminologyService() {
return new SearchParamExtractorDstu1(); return new HapiTerminologySvcDstu1();
} }
} }

View File

@ -31,7 +31,9 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl; import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; 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.SearchParamExtractorDstu2;
import ca.uhn.fhir.jpa.dao.SearchParamRegistryDstu2;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu2; import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu2;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.model.dstu2.composite.MetaDt;
@ -84,6 +86,11 @@ public class BaseDstu2Config extends BaseConfig {
return new SearchParamExtractorDstu2(); return new SearchParamExtractorDstu2();
} }
@Bean
public ISearchParamRegistry searchParamRegistry() {
return new SearchParamRegistryDstu2();
}
@Bean(name = "mySystemDaoDstu2", autowire = Autowire.BY_NAME) @Bean(name = "mySystemDaoDstu2", autowire = Autowire.BY_NAME)
public IFhirSystemDao<ca.uhn.fhir.model.dstu2.resource.Bundle, MetaDt> systemDaoDstu2() { public IFhirSystemDao<ca.uhn.fhir.model.dstu2.resource.Bundle, MetaDt> systemDaoDstu2() {
ca.uhn.fhir.jpa.dao.FhirSystemDaoDstu2 retVal = new ca.uhn.fhir.jpa.dao.FhirSystemDaoDstu2(); ca.uhn.fhir.jpa.dao.FhirSystemDaoDstu2 retVal = new ca.uhn.fhir.jpa.dao.FhirSystemDaoDstu2();

View File

@ -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 not use this file except in compliance with the License.
* You may obtain a copy of the License at * 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 * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * 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.FulltextSearchSvcImpl;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; 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.SearchParamExtractorDstu3;
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu3; import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu3;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
@ -50,25 +52,15 @@ import ca.uhn.fhir.validation.IValidatorModule;
@EnableTransactionManagement @EnableTransactionManagement
public class BaseDstu3Config extends BaseConfig { 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 @Bean
@Primary @Primary
public FhirContext fhirContextDstu3() { public FhirContext fhirContextDstu3() {
FhirContext retVal = FhirContext.forDstu3(); FhirContext retVal = FhirContext.forDstu3();
// Don't strip versions in some places // Don't strip versions in some places
ParserOptions parserOptions = retVal.getParserOptions(); ParserOptions parserOptions = retVal.getParserOptions();
parserOptions.setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.reference"); parserOptions.setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.reference");
return retVal; return retVal;
} }
@ -93,6 +85,16 @@ public class BaseDstu3Config extends BaseConfig {
return searchDao; 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) @Bean(name = "mySystemDaoDstu3", autowire = Autowire.BY_NAME)
public IFhirSystemDao<org.hl7.fhir.dstu3.model.Bundle, org.hl7.fhir.dstu3.model.Meta> systemDaoDstu3() { public IFhirSystemDao<org.hl7.fhir.dstu3.model.Bundle, org.hl7.fhir.dstu3.model.Meta> systemDaoDstu3() {
ca.uhn.fhir.jpa.dao.dstu3.FhirSystemDaoDstu3 retVal = new ca.uhn.fhir.jpa.dao.dstu3.FhirSystemDaoDstu3(); 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; 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 @Primary
@Bean(autowire = Autowire.BY_NAME, name = "myJpaValidationSupportChainDstu3") @Bean(autowire = Autowire.BY_NAME, name = "myJpaValidationSupportChainDstu3")
public IValidationSupport validationSupportChainDstu3() { public IValidationSupport validationSupportChainDstu3() {
return new JpaValidationSupportChainDstu3(); 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;
}
} }

View File

@ -130,7 +130,13 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.MethodUtil; import ca.uhn.fhir.rest.method.MethodUtil;
import ca.uhn.fhir.rest.method.QualifiedParamList; import ca.uhn.fhir.rest.method.QualifiedParamList;
import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.UriAndListParam;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -148,16 +154,17 @@ import ca.uhn.fhir.util.OperationOutcomeUtil;
public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao { public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
private static final String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest";
public static final long INDEX_STATUS_INDEXED = Long.valueOf(1L); public static final long INDEX_STATUS_INDEXED = Long.valueOf(1L);
public static final long INDEX_STATUS_INDEXING_FAILED = Long.valueOf(2L); public static final long INDEX_STATUS_INDEXING_FAILED = Long.valueOf(2L);
public static final String NS_JPA_PROFILE = "https://github.com/jamesagnew/hapi-fhir/ns/jpa/profile"; public static final String NS_JPA_PROFILE = "https://github.com/jamesagnew/hapi-fhir/ns/jpa/profile";
public static final String OO_SEVERITY_ERROR = "error"; public static final String OO_SEVERITY_ERROR = "error";
public static final String OO_SEVERITY_INFO = "information"; public static final String OO_SEVERITY_INFO = "information";
public static final String OO_SEVERITY_WARN = "warning"; public static final String OO_SEVERITY_WARN = "warning";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirDao.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirDao.class);
private static final Map<FhirVersionEnum, FhirContext> ourRetrievalContexts = new HashMap<FhirVersionEnum, FhirContext>(); private static final Map<FhirVersionEnum, FhirContext> ourRetrievalContexts = new HashMap<FhirVersionEnum, FhirContext>();
private static final String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest";
/** /**
* These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(Map)} * These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(Map)}
*/ */
@ -166,8 +173,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
* These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(Map)} * These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(Map)}
*/ */
static final Map<String, Class<? extends IQueryParameterType>> RESOURCE_META_PARAMS; static final Map<String, Class<? extends IQueryParameterType>> RESOURCE_META_PARAMS;
public static final String UCUM_NS = "http://unitsofmeasure.org"; public static final String UCUM_NS = "http://unitsofmeasure.org";
static { static {
Map<String, Class<? extends IQueryParameterType>> resourceMetaParams = new HashMap<String, Class<? extends IQueryParameterType>>(); Map<String, Class<? extends IQueryParameterType>> resourceMetaParams = new HashMap<String, Class<? extends IQueryParameterType>>();
Map<String, Class<? extends IQueryParameterAnd<?>>> resourceMetaAndParams = new HashMap<String, Class<? extends IQueryParameterAnd<?>>>(); Map<String, Class<? extends IQueryParameterAnd<?>>> resourceMetaAndParams = new HashMap<String, Class<? extends IQueryParameterAnd<?>>>();
@ -184,7 +191,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
RESOURCE_META_PARAMS = Collections.unmodifiableMap(resourceMetaParams); RESOURCE_META_PARAMS = Collections.unmodifiableMap(resourceMetaParams);
RESOURCE_META_AND_PARAMS = Collections.unmodifiableMap(resourceMetaAndParams); RESOURCE_META_AND_PARAMS = Collections.unmodifiableMap(resourceMetaAndParams);
} }
@Autowired(required = true) @Autowired(required = true)
private DaoConfig myConfig; private DaoConfig myConfig;
@ -213,9 +219,18 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
@Autowired @Autowired
private ISearchParamExtractor mySearchParamExtractor; private ISearchParamExtractor mySearchParamExtractor;
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
@Autowired @Autowired
private ISearchResultDao mySearchResultDao; private ISearchResultDao mySearchResultDao;
protected void clearRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) {
if (theRequestDetails != null) {
theRequestDetails.getUserData().remove(PROCESSING_SUB_REQUEST);
}
}
protected void createForcedIdIfNeeded(ResourceTable theEntity, IIdType theId) { protected void createForcedIdIfNeeded(ResourceTable theEntity, IIdType theId) {
if (theId.isEmpty() == false && theId.hasIdPart()) { if (theId.isEmpty() == false && theId.hasIdPart()) {
if (isValidPid(theId)) { if (isValidPid(theId)) {
@ -530,6 +545,17 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return (IFhirResourceDao<R>) myResourceTypeToDao.get(theType); return (IFhirResourceDao<R>) myResourceTypeToDao.get(theType);
} }
@Override
public RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName) {
Map<String, RuntimeSearchParam> params = mySearchParamRegistry.getActiveSearchParams(theResourceDef.getName());
return params.get(theParamName);
}
@Override
public Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) {
return mySearchParamRegistry.getActiveSearchParams(theResourceDef.getName()).values();
}
protected TagDefinition getTag(TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) { protected TagDefinition getTag(TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<TagDefinition> cq = builder.createQuery(TagDefinition.class); CriteriaQuery<TagDefinition> cq = builder.createQuery(TagDefinition.class);
@ -646,6 +672,12 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
theProvider.setSearchResultDao(mySearchResultDao); theProvider.setSearchResultDao(mySearchResultDao);
} }
protected void markRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) {
if (theRequestDetails != null) {
theRequestDetails.getUserData().put(PROCESSING_SUB_REQUEST, Boolean.TRUE);
}
}
protected void notifyInterceptors(RestOperationTypeEnum theOperationType, ActionRequestDetails theRequestDetails) { protected void notifyInterceptors(RestOperationTypeEnum theOperationType, ActionRequestDetails theRequestDetails) {
if (theRequestDetails.getId() != null && theRequestDetails.getId().hasResourceType() && isNotBlank(theRequestDetails.getResourceType())) { if (theRequestDetails.getId() != null && theRequestDetails.getId().hasResourceType() && isNotBlank(theRequestDetails.getResourceType())) {
if (theRequestDetails.getId().getResourceType().equals(theRequestDetails.getResourceType()) == false) { if (theRequestDetails.getId().getResourceType().equals(theRequestDetails.getResourceType()) == false) {
@ -666,18 +698,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
} }
} }
protected void markRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) {
if (theRequestDetails != null) {
theRequestDetails.getUserData().put(PROCESSING_SUB_REQUEST, Boolean.TRUE);
}
}
protected void clearRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) {
if (theRequestDetails != null) {
theRequestDetails.getUserData().remove(PROCESSING_SUB_REQUEST);
}
}
public String parseContentTextIntoWords(IBaseResource theResource) { public String parseContentTextIntoWords(IBaseResource theResource) {
StringBuilder retVal = new StringBuilder(); StringBuilder retVal = new StringBuilder();
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@ -914,7 +934,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
} }
/** /**
* Subclasses may override to provide behaviour. Called when a resource has been inserved into the database for the first time. * Subclasses may override to provide behaviour. Called when a resource has been inserted into the database for the first time.
* *
* @param theEntity * @param theEntity
* The entity being updated (Do not modify the entity! Undefined behaviour will occur!) * The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
@ -928,7 +948,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
} }
/** /**
* Subclasses may override to provide behaviour. Called when a resource has been inserved into the database for the first time. * Subclasses may override to provide behaviour. Called when a pre-existing resource has been updated in the database
* *
* @param theEntity * @param theEntity
* The resource * The resource
@ -943,7 +963,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
public <R extends IBaseResource> Set<Long> processMatchUrl(String theMatchUrl, Class<R> theResourceType) { public <R extends IBaseResource> Set<Long> processMatchUrl(String theMatchUrl, Class<R> theResourceType) {
RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(theResourceType); RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(theResourceType);
SearchParameterMap paramMap = translateMatchUrl(myContext, theMatchUrl, resourceDef); SearchParameterMap paramMap = translateMatchUrl(this, myContext, theMatchUrl, resourceDef);
paramMap.setPersistResults(false); paramMap.setPersistResults(false);
if (paramMap.isEmpty() && paramMap.getLastUpdated() == null) { if (paramMap.isEmpty() && paramMap.getLastUpdated() == null) {
@ -962,24 +982,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
throw new NotImplementedException(""); throw new NotImplementedException("");
} }
// protected MetaDt toMetaDt(Collection<TagDefinition> tagDefinitions) {
// MetaDt retVal = new MetaDt();
// for (TagDefinition next : tagDefinitions) {
// switch (next.getTagType()) {
// case PROFILE:
// retVal.addProfile(next.getCode());
// break;
// case SECURITY_LABEL:
// retVal.addSecurity().setSystem(next.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay());
// break;
// case TAG:
// retVal.addTag().setSystem(next.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay());
// break;
// }
// }
// return retVal;
// }
public void setConfig(DaoConfig theConfig) { public void setConfig(DaoConfig theConfig) {
myConfig = theConfig; myConfig = theConfig;
} }
@ -1698,27 +1700,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
} }
} }
protected static List<NameValuePair> translateMatchUrl(String theMatchUrl) { public static SearchParameterMap translateMatchUrl(IDao theCallingDao, FhirContext theContext, String theMatchUrl, RuntimeResourceDefinition resourceDef) {
List<NameValuePair> parameters;
String matchUrl = theMatchUrl;
int questionMarkIndex = matchUrl.indexOf('?');
if (questionMarkIndex != -1) {
matchUrl = matchUrl.substring(questionMarkIndex + 1);
}
matchUrl = matchUrl.replace("|", "%7C");
matchUrl = matchUrl.replace("=>=", "=%3E%3D");
matchUrl = matchUrl.replace("=<=", "=%3C%3D");
matchUrl = matchUrl.replace("=>", "=%3E");
matchUrl = matchUrl.replace("=<", "=%3C");
if (matchUrl.contains(" ")) {
throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - URL is invalid (must not contain spaces)");
}
parameters = URLEncodedUtils.parse((matchUrl), Constants.CHARSET_UTF8, '&');
return parameters;
}
public static SearchParameterMap translateMatchUrl(FhirContext theContext, String theMatchUrl, RuntimeResourceDefinition resourceDef) {
SearchParameterMap paramMap = new SearchParameterMap(); SearchParameterMap paramMap = new SearchParameterMap();
List<NameValuePair> parameters = translateMatchUrl(theMatchUrl); List<NameValuePair> parameters = translateMatchUrl(theMatchUrl);
@ -1788,7 +1770,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
} else if (nextParamName.startsWith("_")) { } else if (nextParamName.startsWith("_")) {
// ignore these since they aren't search params (e.g. _sort) // ignore these since they aren't search params (e.g. _sort)
} else { } else {
RuntimeSearchParam paramDef = resourceDef.getSearchParam(nextParamName); RuntimeSearchParam paramDef = theCallingDao.getSearchParamByName(resourceDef, nextParamName);
if (paramDef == null) { if (paramDef == null) {
throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName); throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName);
} }
@ -1800,6 +1782,26 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return paramMap; return paramMap;
} }
protected static List<NameValuePair> translateMatchUrl(String theMatchUrl) {
List<NameValuePair> parameters;
String matchUrl = theMatchUrl;
int questionMarkIndex = matchUrl.indexOf('?');
if (questionMarkIndex != -1) {
matchUrl = matchUrl.substring(questionMarkIndex + 1);
}
matchUrl = matchUrl.replace("|", "%7C");
matchUrl = matchUrl.replace("=>=", "=%3E%3D");
matchUrl = matchUrl.replace("=<=", "=%3C%3D");
matchUrl = matchUrl.replace("=>", "=%3E");
matchUrl = matchUrl.replace("=<", "=%3C");
if (matchUrl.contains(" ")) {
throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - URL is invalid (must not contain spaces)");
}
parameters = URLEncodedUtils.parse((matchUrl), Constants.CHARSET_UTF8, '&');
return parameters;
}
public static void validateResourceType(BaseHasResource theEntity, String theResourceName) { public static void validateResourceType(BaseHasResource theEntity, String theResourceName) {
if (!theResourceName.equals(theEntity.getResourceType())) { if (!theResourceName.equals(theEntity.getResourceType())) {
throw new ResourceNotFoundException( throw new ResourceNotFoundException(

View File

@ -22,15 +22,29 @@ package ca.uhn.fhir.jpa.dao;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.*; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.persistence.NoResultException; import javax.persistence.NoResultException;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseMetaType;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required; import org.springframework.beans.factory.annotation.Required;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
@ -43,22 +57,44 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.entity.BaseHasResource;
import ca.uhn.fhir.jpa.entity.BaseTag;
import ca.uhn.fhir.jpa.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.entity.ResourceLink;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.TagDefinition;
import ca.uhn.fhir.jpa.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.interceptor.IJpaServerInterceptor; import ca.uhn.fhir.jpa.interceptor.IJpaServerInterceptor;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.jpa.util.DeleteConflict; import ca.uhn.fhir.jpa.util.DeleteConflict;
import ca.uhn.fhir.jpa.util.StopWatch; import ca.uhn.fhir.jpa.util.StopWatch;
import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils; import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils;
import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils; import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils;
import ca.uhn.fhir.model.api.*; import ca.uhn.fhir.model.api.IQueryParameterAnd;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.PatchTypeEnum; import ca.uhn.fhir.rest.api.PatchTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.MethodUtil;
import ca.uhn.fhir.rest.method.QualifiedParamList;
import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.method.SearchMethodBinding;
import ca.uhn.fhir.rest.method.SearchMethodBinding.QualifierDetails;
import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.*; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.FhirTerser; import ca.uhn.fhir.util.FhirTerser;
@ -74,6 +110,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Autowired @Autowired
private DaoConfig myDaoConfig; private DaoConfig myDaoConfig;
@Autowired @Autowired
protected IResourceTableDao myResourceTableDao;
@Autowired
protected PlatformTransactionManager myPlatformTransactionManager; protected PlatformTransactionManager myPlatformTransactionManager;
@Autowired @Autowired
private IResourceHistoryTableDao myResourceHistoryTableDao; private IResourceHistoryTableDao myResourceHistoryTableDao;
@ -86,6 +124,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Autowired() @Autowired()
protected ISearchResultDao mySearchResultDao; protected ISearchResultDao mySearchResultDao;
private String mySecondaryPrimaryKeyParamName; private String mySecondaryPrimaryKeyParamName;
@Autowired
private ISearchParamRegistry mySerarchParamRegistry;
@Autowired() @Autowired()
protected IHapiTerminologySvc myTerminologySvc; protected IHapiTerminologySvc myTerminologySvc;
@ -662,13 +702,37 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return retVal; return retVal;
} }
@Override
public DaoMethodOutcome patch(IIdType theId, PatchTypeEnum thePatchType, String thePatchBody, RequestDetails theRequestDetails) {
ResourceTable entityToUpdate = readEntityLatestVersion(theId);
if (theId.hasVersionIdPart()) {
if (theId.getVersionIdPartAsLong() != entityToUpdate.getVersion()) {
throw new ResourceVersionConflictException("Version " + theId.getVersionIdPart() + " is not the most recent version of this resource, unable to apply patch");
}
}
validateResourceType(entityToUpdate);
IBaseResource resourceToUpdate = toResource(entityToUpdate, false);
IBaseResource destination;
if (thePatchType == PatchTypeEnum.JSON_PATCH) {
destination = JsonPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody);
} else {
destination = XmlPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody);
}
@SuppressWarnings("unchecked")
T destinationCasted = (T) destination;
return update(destinationCasted, null, true, theRequestDetails);
}
@PostConstruct @PostConstruct
public void postConstruct() { public void postConstruct() {
RuntimeResourceDefinition def = getContext().getResourceDefinition(myResourceType); RuntimeResourceDefinition def = getContext().getResourceDefinition(myResourceType);
myResourceName = def.getName(); myResourceName = def.getName();
if (mySecondaryPrimaryKeyParamName != null) { if (mySecondaryPrimaryKeyParamName != null) {
RuntimeSearchParam sp = def.getSearchParam(mySecondaryPrimaryKeyParamName); RuntimeSearchParam sp = getSearchParamByName(def, mySecondaryPrimaryKeyParamName);
if (sp == null) { if (sp == null) {
throw new ConfigurationException("Unknown search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "]"); throw new ConfigurationException("Unknown search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "]");
} }
@ -870,7 +934,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
notifyInterceptors(RestOperationTypeEnum.SEARCH_TYPE, requestDetails); notifyInterceptors(RestOperationTypeEnum.SEARCH_TYPE, requestDetails);
SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this, myResourceIndexedSearchParamUriDao, myForcedIdDao, SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this, myResourceIndexedSearchParamUriDao, myForcedIdDao,
myTerminologySvc); myTerminologySvc, mySerarchParamRegistry);
builder.setType(getResourceType(), getResourceName()); builder.setType(getResourceType(), getResourceName());
return builder.search(theParams); return builder.search(theParams);
} }
@ -899,7 +963,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
theParams.setPersistResults(false); theParams.setPersistResults(false);
SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this, myResourceIndexedSearchParamUriDao, myForcedIdDao, SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this, myResourceIndexedSearchParamUriDao, myForcedIdDao,
myTerminologySvc); myTerminologySvc, mySerarchParamRegistry);
builder.setType(getResourceType(), getResourceName()); builder.setType(getResourceType(), getResourceName());
builder.search(theParams); builder.search(theParams);
return builder.doGetPids(); return builder.doGetPids();
@ -985,6 +1049,39 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return retVal; return retVal;
} }
@Override
public void translateRawParameters(Map<String, List<String>> theSource, SearchParameterMap theTarget) {
if (theSource == null || theSource.isEmpty()) {
return;
}
Map<String, RuntimeSearchParam> searchParams = mySerarchParamRegistry.getActiveSearchParams(getResourceName());
Set<String> paramNames = theSource.keySet();
for (String nextParamName : paramNames) {
QualifierDetails qualifiedParamName = SearchMethodBinding.extractQualifiersFromParameterName(nextParamName);
RuntimeSearchParam param = searchParams.get(qualifiedParamName.getParamName());
if (param == null) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "invalidSearchParameter", qualifiedParamName.getParamName(), new TreeSet<String>(searchParams.keySet()));
throw new InvalidRequestException(msg);
}
// Should not be null since the check above would have caught it
RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(myResourceName);
RuntimeSearchParam paramDef = getSearchParamByName(resourceDef, qualifiedParamName.getParamName());
for (String nextValue : theSource.get(nextParamName)) {
if (isNotBlank(nextValue)) {
QualifiedParamList qualifiedParam = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(qualifiedParamName.getWholeQualifier(), nextValue);
List<QualifiedParamList> paramList = Collections.singletonList(qualifiedParam);
IQueryParameterAnd<?> parsedParam = MethodUtil.parseQueryParams(getContext(), paramDef, nextParamName, paramList);
theTarget.add(qualifiedParamName.getParamName(), parsedParam);
}
}
}
}
@Override @Override
public DaoMethodOutcome update(T theResource) { public DaoMethodOutcome update(T theResource) {
return update(theResource, null, null); return update(theResource, null, null);
@ -1075,30 +1172,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
ourLog.info(msg); ourLog.info(msg);
return outcome; return outcome;
} }
@Override
public DaoMethodOutcome patch(IIdType theId, PatchTypeEnum thePatchType, String thePatchBody, RequestDetails theRequestDetails) {
ResourceTable entityToUpdate = readEntityLatestVersion(theId);
if (theId.hasVersionIdPart()) {
if (theId.getVersionIdPartAsLong() != entityToUpdate.getVersion()) {
throw new ResourceVersionConflictException("Version " + theId.getVersionIdPart() + " is not the most recent version of this resource, unable to apply patch");
}
}
validateResourceType(entityToUpdate);
IBaseResource resourceToUpdate = toResource(entityToUpdate, false);
IBaseResource destination;
if (thePatchType == PatchTypeEnum.JSON_PATCH) {
destination = JsonPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody);
} else {
destination = XmlPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody);
}
@SuppressWarnings("unchecked")
T destinationCasted = (T) destination;
return update(destinationCasted, null, true, theRequestDetails);
}
@Override @Override

View File

@ -21,9 +21,12 @@ package ca.uhn.fhir.jpa.dao;
*/ */
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.apache.commons.lang3.ObjectUtils;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -37,17 +40,37 @@ import ca.uhn.fhir.util.FhirTerser;
public abstract class BaseSearchParamExtractor implements ISearchParamExtractor { public abstract class BaseSearchParamExtractor implements ISearchParamExtractor {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseSearchParamExtractor.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseSearchParamExtractor.class);
protected static final Pattern SPLIT = Pattern.compile("\\||( or )"); public static final Pattern SPLIT = Pattern.compile("\\||( or )");
@Autowired @Autowired
private FhirContext myContext; private FhirContext myContext;
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
public BaseSearchParamExtractor() { public BaseSearchParamExtractor() {
super(); super();
} }
public BaseSearchParamExtractor(FhirContext theCtx) { public BaseSearchParamExtractor(FhirContext theCtx, ISearchParamRegistry theSearchParamRegistry) {
myContext = theCtx; myContext = theCtx;
mySearchParamRegistry = theSearchParamRegistry;
}
@Override
public List<PathAndRef> extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) {
List<PathAndRef> refs = new ArrayList<PathAndRef>();
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<Object> extractValues(String thePaths, IBaseResource theResource) { protected List<Object> extractValues(String thePaths, IBaseResource theResource) {
@ -70,26 +93,18 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
return myContext; return myContext;
} }
protected Collection<RuntimeSearchParam> getSearchParams(IBaseResource theResource) {
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource);
Collection<RuntimeSearchParam> retVal = mySearchParamRegistry.getActiveSearchParams(def.getName()).values();
List<RuntimeSearchParam> defaultList= Collections.emptyList();
retVal = ObjectUtils.defaultIfNull(retVal, defaultList);
return retVal;
}
@VisibleForTesting @VisibleForTesting
void setContextForUnitTest(FhirContext theContext) { void setContextForUnitTest(FhirContext theContext) {
myContext = theContext; myContext = theContext;
} }
@Override
public List<PathAndRef> extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) {
List<PathAndRef> refs = new ArrayList<PathAndRef>();
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;
}
} }

View File

@ -0,0 +1,77 @@
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.ObjectUtils;
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 static final Map<String, RuntimeSearchParam> EMPTY_SP_MAP = Collections.emptyMap();
private Map<String, Map<String, RuntimeSearchParam>> myBuiltInSearchParams;
@Autowired
private FhirContext myCtx;
@Autowired
private Collection<IFhirResourceDao<?>> myDaos;
public BaseSearchParamRegistry() {
super();
}
@Override
public void forceRefresh() {
// nothing by default
}
public Map<String, Map<String, RuntimeSearchParam>> getBuiltInSearchParams() {
return myBuiltInSearchParams;
}
@Override
public Map<String,RuntimeSearchParam> getActiveSearchParams(String theResourceName) {
Validate.notBlank(theResourceName, "theResourceName must not be blank or null");
return myBuiltInSearchParams.get(theResourceName);
}
@PostConstruct
public void postConstruct() {
Map<String, Map<String, RuntimeSearchParam>> resourceNameToSearchParams = new HashMap<String, Map<String,RuntimeSearchParam>>();
for (IFhirResourceDao<?> nextDao : myDaos) {
RuntimeResourceDefinition nextResDef = myCtx.getResourceDefinition(nextDao.getResourceType());
String nextResourceName = nextResDef.getName();
HashMap<String, RuntimeSearchParam> nameToParam = new HashMap<String, RuntimeSearchParam>();
resourceNameToSearchParams.put(nextResourceName, nameToParam);
for (RuntimeSearchParam nextSp : nextResDef.getSearchParams()) {
nameToParam.put(nextSp.getName(), nextSp);
}
}
myBuiltInSearchParams = Collections.unmodifiableMap(resourceNameToSearchParams);
}
@Override
public Collection<RuntimeSearchParam> getAllSearchParams(String theResourceName) {
Validate.notBlank(theResourceName, "theResourceName must not be null or blank");
Map<String, RuntimeSearchParam> map = myBuiltInSearchParams.get(theResourceName);
map = ObjectUtils.defaultIfNull(map, EMPTY_SP_MAP);
return Collections.unmodifiableCollection(map.values());
}
}

View File

@ -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 not use this file except in compliance with the License.
* You may obtain a copy of the License at * 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 * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -35,26 +35,28 @@ public class DaoConfig {
// *** // ***
// update setter javadoc if default changes // update setter javadoc if default changes
// *** // ***
private boolean myAllowExternalReferences = false; private boolean myAllowExternalReferences = false;
// *** // ***
// update setter javadoc if default changes // update setter javadoc if default changes
// *** // ***
private boolean myAllowInlineMatchUrlReferences = false; private boolean myAllowInlineMatchUrlReferences = false;
private boolean myAllowMultipleDelete; private boolean myAllowMultipleDelete;
private boolean myDefaultSearchParamsCanBeOverridden = false;
// *** // ***
// update setter javadoc if default changes // update setter javadoc if default changes
// *** // ***
private int myDeferIndexingForCodesystemsOfSize = 2000; private int myDeferIndexingForCodesystemsOfSize = 2000;
private boolean myDeleteStaleSearches = true; private boolean myDeleteStaleSearches = true;
// *** // ***
// update setter javadoc if default changes // update setter javadoc if default changes
// *** // ***
private long myExpireSearchResultsAfterMillis = DateUtils.MILLIS_PER_HOUR; private long myExpireSearchResultsAfterMillis = DateUtils.MILLIS_PER_HOUR;
private int myHardTagListLimit = 1000; private int myHardTagListLimit = 1000;
private int myIncludeLimit = 2000; private int myIncludeLimit = 2000;
// *** // ***
@ -63,19 +65,19 @@ public class DaoConfig {
private boolean myIndexContainedResources = true; private boolean myIndexContainedResources = true;
private List<IServerInterceptor> myInterceptors; private List<IServerInterceptor> myInterceptors;
// *** // ***
// update setter javadoc if default changes // update setter javadoc if default changes
// *** // ***
private int myMaximumExpansionSize = 5000; private int myMaximumExpansionSize = 5000;
private ResourceEncodingEnum myResourceEncoding = ResourceEncodingEnum.JSONC; private ResourceEncodingEnum myResourceEncoding = ResourceEncodingEnum.JSONC;
private boolean mySchedulingDisabled; private boolean mySchedulingDisabled;
private boolean mySubscriptionEnabled; private boolean mySubscriptionEnabled;
private long mySubscriptionPollDelay = 1000; private long mySubscriptionPollDelay = 1000;
private Long mySubscriptionPurgeInactiveAfterMillis; private Long mySubscriptionPurgeInactiveAfterMillis;
private Set<String> myTreatBaseUrlsAsLocal = new HashSet<String>(); private Set<String> myTreatBaseUrlsAsLocal = new HashSet<String>();
@ -91,42 +93,44 @@ public class DaoConfig {
public int getDeferIndexingForCodesystemsOfSize() { public int getDeferIndexingForCodesystemsOfSize() {
return myDeferIndexingForCodesystemsOfSize; 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. * should be preserved before being purged from the database.
* <p> * <p>
* 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 * requests. After this
* number of milliseconds, they will be deleted from the database, and any paging links * 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.
* </p> * </p>
* <p> * <p>
*
* @see To disable this feature entirely, see {@link #setExpireSearchResults(boolean)} * @see To disable this feature entirely, see {@link #setExpireSearchResults(boolean)}
* </p> * </p>
* *
* @since 1.5 * @since 1.5
*/ */
public long getExpireSearchResultsAfterMillis() { public long getExpireSearchResultsAfterMillis() {
return myExpireSearchResultsAfterMillis; return myExpireSearchResultsAfterMillis;
} }
/** /**
* Gets the maximum number of results to return in a GetTags query (DSTU1 only) * Gets the maximum number of results to return in a GetTags query (DSTU1 only)
*/ */
public int getHardTagListLimit() { public int getHardTagListLimit() {
return myHardTagListLimit; return myHardTagListLimit;
} }
public int getIncludeLimit() { public int getIncludeLimit() {
return myIncludeLimit; return myIncludeLimit;
} }
/** /**
* Returns the interceptors which will be notified of operations. * Returns the interceptors which will be notified of operations.
* *
* @see #setInterceptors(List) * @see #setInterceptors(List)
* @deprecated As of 2.2 this method is deprecated. There is no good reason to register an interceptor * @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 @Deprecated
public List<IServerInterceptor> getInterceptors() { public List<IServerInterceptor> getInterceptors() {
@ -135,22 +139,26 @@ public class DaoConfig {
} }
return myInterceptors; return myInterceptors;
} }
/** /**
* See {@link #setMaximumExpansionSize(int)} * See {@link #setMaximumExpansionSize(int)}
*/ */
public int getMaximumExpansionSize() { public int getMaximumExpansionSize() {
return myMaximumExpansionSize; return myMaximumExpansionSize;
} }
public ResourceEncodingEnum getResourceEncoding() { public ResourceEncodingEnum getResourceEncoding() {
return myResourceEncoding; return myResourceEncoding;
} }
public long getSubscriptionPollDelay() { public long getSubscriptionPollDelay() {
return mySubscriptionPollDelay; return mySubscriptionPollDelay;
} }
public Long getSubscriptionPurgeInactiveAfterMillis() { public Long getSubscriptionPurgeInactiveAfterMillis() {
return mySubscriptionPurgeInactiveAfterMillis; return mySubscriptionPurgeInactiveAfterMillis;
} }
/** /**
* This setting may be used to advise the server that any references found in * 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 * resources that have any of the base URLs given here will be replaced with
@ -165,6 +173,7 @@ public class DaoConfig {
public Set<String> getTreatBaseUrlsAsLocal() { public Set<String> getTreatBaseUrlsAsLocal() {
return myTreatBaseUrlsAsLocal; return myTreatBaseUrlsAsLocal;
} }
/** /**
* If set to <code>true</code> (default is <code>false</code>) the server will allow * If set to <code>true</code> (default is <code>false</code>) the server will allow
* resources to have references to external servers. For example if this server is * resources to have references to external servers. For example if this server is
@ -179,7 +188,7 @@ public class DaoConfig {
* <p> * <p>
* Note that external references will be indexed by the server and may be searched * Note that external references will be indexed by the server and may be searched
* (e.g. <code>Patient:organization</code>), but * (e.g. <code>Patient:organization</code>), but
* chained searches (e.g. <code>Patient:organization.name</code>) will not work across * chained searches (e.g. <code>Patient:organization.name</code>) will not work across
* these references. * these references.
* </p> * </p>
* <p> * <p>
@ -193,19 +202,37 @@ public class DaoConfig {
public boolean isAllowExternalReferences() { public boolean isAllowExternalReferences() {
return myAllowExternalReferences; return myAllowExternalReferences;
} }
/** /**
* @see #setAllowInlineMatchUrlReferences(boolean) * @see #setAllowInlineMatchUrlReferences(boolean)
*/ */
public boolean isAllowInlineMatchUrlReferences() { public boolean isAllowInlineMatchUrlReferences() {
return myAllowInlineMatchUrlReferences; return myAllowInlineMatchUrlReferences;
} }
public boolean isAllowMultipleDelete() { public boolean isAllowMultipleDelete() {
return myAllowMultipleDelete; return myAllowMultipleDelete;
} }
/** /**
* If this is set to <code>false</code> (default is <code>true</code>) the stale search deletion * If set to {@code true} the default search params (i.e. the search parameters that are
* task will be disabled (meaning that search results will be retained in the database indefinitely). USE WITH CAUTION. * 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.
* <p>
* This can be useful if you want to be able to disable or alter
* the behaviour of the default search parameters.
* </p>
* <p>
* The default value for this setting is {@code false}
* </p>
*/
public boolean isDefaultSearchParamsCanBeOverridden() {
return myDefaultSearchParamsCanBeOverridden;
}
/**
* If this is set to <code>false</code> (default is <code>true</code>) the stale search deletion
* task will be disabled (meaning that search results will be retained in the database indefinitely). USE WITH CAUTION.
* <p> * <p>
* This feature is useful if you want to define your own process for deleting these (e.g. because * This feature is useful if you want to define your own process for deleting these (e.g. because
* you are running in a cluster) * 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 * Should contained IDs be indexed the same way that non-contained IDs are (default is
* <code>true</code>) * <code>true</code>)
*/ */
public boolean isIndexContainedResources() { public boolean isIndexContainedResources() {
return myIndexContainedResources; return myIndexContainedResources;
@ -248,7 +275,7 @@ public class DaoConfig {
* <p> * <p>
* Note that external references will be indexed by the server and may be searched * Note that external references will be indexed by the server and may be searched
* (e.g. <code>Patient:organization</code>), but * (e.g. <code>Patient:organization</code>), but
* chained searches (e.g. <code>Patient:organization.name</code>) will not work across * chained searches (e.g. <code>Patient:organization.name</code>) will not work across
* these references. * these references.
* </p> * </p>
* <p> * <p>
@ -271,6 +298,7 @@ public class DaoConfig {
* <p> * <p>
* Default is false for now, as this is an experimental feature. * Default is false for now, as this is an experimental feature.
* </p> * </p>
*
* @since 1.5 * @since 1.5
*/ */
public void setAllowInlineMatchUrlReferences(boolean theAllowInlineMatchUrlReferences) { public void setAllowInlineMatchUrlReferences(boolean theAllowInlineMatchUrlReferences) {
@ -281,6 +309,22 @@ public class DaoConfig {
myAllowMultipleDelete = theAllowMultipleDelete; 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.
* <p>
* This can be useful if you want to be able to disable or alter
* the behaviour of the default search parameters.
* </p>
* <p>
* The default value for this setting is {@code false}
* </p>
*/
public void setDefaultSearchParamsCanBeOverridden(boolean theDefaultSearchParamsCanBeOverridden) {
myDefaultSearchParamsCanBeOverridden = theDefaultSearchParamsCanBeOverridden;
}
/** /**
* When a code system is added that contains more than this number of codes, * 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 * 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 <code>false</code> (default is <code>true</code>) the stale search deletion * If this is set to <code>false</code> (default is <code>true</code>) the stale search deletion
* task will be disabled (meaning that search results will be retained in the database indefinitely). USE WITH CAUTION. * task will be disabled (meaning that search results will be retained in the database indefinitely). USE WITH CAUTION.
* <p> * <p>
* This feature is useful if you want to define your own process for deleting these (e.g. because * This feature is useful if you want to define your own process for deleting these (e.g. because
* you are running in a cluster) * 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. * should be preserved before being purged from the database.
* <p> * <p>
* 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 * requests. After this
* number of milliseconds, they will be deleted from the database, and any paging links * 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.
* </p> * </p>
* <p> * <p>
*
* @see To disable this feature entirely, see {@link #setExpireSearchResults(boolean)} * @see To disable this feature entirely, see {@link #setExpireSearchResults(boolean)}
* </p> * </p>
* @since 1.5 * @since 1.5
*/ */
public void setExpireSearchResultsAfterMillis(long theExpireSearchResultsAfterMillis) { public void setExpireSearchResultsAfterMillis(long theExpireSearchResultsAfterMillis) {
@ -329,7 +374,7 @@ public class DaoConfig {
* paging provider instead. * paging provider instead.
* *
* @deprecated This method does not do anything. Configure the page size on your * @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 @Deprecated
public void setHardSearchLimit(@SuppressWarnings("unused") int theHardSearchLimit) { 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 * Should contained IDs be indexed the same way that non-contained IDs are (default is
* <code>true</code>) * <code>true</code>)
*/ */
public void setIndexContainedResources(boolean theIndexContainedResources) { public void setIndexContainedResources(boolean theIndexContainedResources) {
myIndexContainedResources = theIndexContainedResources; myIndexContainedResources = theIndexContainedResources;
@ -362,8 +407,9 @@ public class DaoConfig {
/** /**
* This may be used to optionally register server interceptors directly against the DAOs. * 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 * @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 @Deprecated
public void setInterceptors(IServerInterceptor... theInterceptor) { 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. * 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 * @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 @Deprecated
public void setInterceptors(List<IServerInterceptor> theInterceptors) { public void setInterceptors(List<IServerInterceptor> theInterceptors) {
@ -419,11 +466,11 @@ public class DaoConfig {
} }
mySubscriptionPurgeInactiveAfterMillis = theMillis; mySubscriptionPurgeInactiveAfterMillis = theMillis;
} }
public void setSubscriptionPurgeInactiveAfterSeconds(int theSeconds) { public void setSubscriptionPurgeInactiveAfterSeconds(int theSeconds) {
setSubscriptionPurgeInactiveAfterMillis(theSeconds * DateUtils.MILLIS_PER_SECOND); setSubscriptionPurgeInactiveAfterMillis(theSeconds * DateUtils.MILLIS_PER_SECOND);
} }
/** /**
* This setting may be used to advise the server that any references found in * 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 * 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 <code>Patient/1</code> * convert this reference to <code>Patient/1</code>
* </p> * </p>
* *
* @param theTreatBaseUrlsAsLocal The set of base URLs. May be <code>null</code>, which * @param theTreatBaseUrlsAsLocal
* means no references will be treated as external * The set of base URLs. May be <code>null</code>, which
* means no references will be treated as external
*/ */
public void setTreatBaseUrlsAsLocal(Set<String> theTreatBaseUrlsAsLocal) { public void setTreatBaseUrlsAsLocal(Set<String> theTreatBaseUrlsAsLocal) {
HashSet<String> treatBaseUrlsAsLocal = new HashSet<String>(); HashSet<String> treatBaseUrlsAsLocal = new HashSet<String>();

View File

@ -79,7 +79,8 @@ public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResou
values.addAll(theTerser.getAllPopulatedChildElementsOfType(theResource, BaseResourceReferenceDt.class)); values.addAll(theTerser.getAllPopulatedChildElementsOfType(theResource, BaseResourceReferenceDt.class));
} else if (theInclude.getValue().startsWith(theResourceDef.getName() + ":")) { } else if (theInclude.getValue().startsWith(theResourceDef.getName() + ":")) {
values = new ArrayList<Object>(); values = new ArrayList<Object>();
RuntimeSearchParam sp = theResourceDef.getSearchParam(theInclude.getValue().substring(theInclude.getValue().indexOf(':') + 1)); String paramName = theInclude.getValue().substring(theInclude.getValue().indexOf(':') + 1);
RuntimeSearchParam sp = getSearchParamByName(theResourceDef, paramName);
for (String nextPath : sp.getPathsSplit()) { for (String nextPath : sp.getPathsSplit()) {
values.addAll(theTerser.getValues(theResource, nextPath)); values.addAll(theTerser.getValues(theResource, nextPath));
} }

View File

@ -26,6 +26,7 @@ import javax.servlet.http.HttpServletRequest;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType; 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.jpa.dao.SearchParameterMap.EverythingModeEnum;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
@ -46,6 +47,9 @@ public class FhirResourceDaoPatientDstu2 extends FhirResourceDaoDstu2<Patient>im
super(); super();
} }
@Autowired
private ISearchParamRegistry mySerarchParamRegistry;
private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative) { private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative) {
SearchParameterMap paramMap = new SearchParameterMap(); SearchParameterMap paramMap = new SearchParameterMap();
if (theCount != null) { if (theCount != null) {
@ -65,7 +69,7 @@ public class FhirResourceDaoPatientDstu2 extends FhirResourceDaoDstu2<Patient>im
paramMap.add("_id", new StringParam(theId.getIdPart())); 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()); builder.setType(getResourceType(), getResourceName());
return builder.search(paramMap); return builder.search(paramMap);
} }

View File

@ -152,7 +152,7 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
private int pollForNewUndeliveredResources(SubscriptionTable theSubscriptionTable) { private int pollForNewUndeliveredResources(SubscriptionTable theSubscriptionTable) {
Subscription subscription = toResource(Subscription.class, theSubscriptionTable.getSubscriptionResource(), false); Subscription subscription = toResource(Subscription.class, theSubscriptionTable.getSubscriptionResource(), false);
RuntimeResourceDefinition resourceDef = validateCriteriaAndReturnResourceDefinition(subscription); RuntimeResourceDefinition resourceDef = validateCriteriaAndReturnResourceDefinition(subscription);
SearchParameterMap criteriaUrl = translateMatchUrl(getContext(), subscription.getCriteria(), resourceDef); SearchParameterMap criteriaUrl = translateMatchUrl(this, getContext(), subscription.getCriteria(), resourceDef);
criteriaUrl = new SearchParameterMap(); criteriaUrl = new SearchParameterMap();
long start = theSubscriptionTable.getMostRecentMatch().getTime(); long start = theSubscriptionTable.getMostRecentMatch().getTime();

View File

@ -1,11 +1,14 @@
package ca.uhn.fhir.jpa.dao; package ca.uhn.fhir.jpa.dao;
import java.util.Collection;
import java.util.Set; import java.util.Set;
import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.entity.BaseHasResource; import ca.uhn.fhir.jpa.entity.BaseHasResource;
import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
@ -73,4 +76,9 @@ public interface IDao {
<R extends IBaseResource> R toResource(Class<R> theResourceType, BaseHasResource theEntity, boolean theForHistoryOperation); <R extends IBaseResource> R toResource(Class<R> theResourceType, BaseHasResource theEntity, boolean theForHistoryOperation);
void populateFullTextFields(IBaseResource theResource, ResourceTable theEntity); void populateFullTextFields(IBaseResource theResource, ResourceTable theEntity);
RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName);
Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef);
} }

View File

@ -39,8 +39,10 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.PatchTypeEnum; import ca.uhn.fhir.rest.api.PatchTypeEnum;
import ca.uhn.fhir.rest.api.ValidationModeEnum; import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
public interface IFhirResourceDao<T extends IBaseResource> extends IDao { public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
@ -134,6 +136,8 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
*/ */
<MT extends IBaseMetaType> MT metaGetOperation(Class<MT> theType, RequestDetails theRequestDetails); <MT extends IBaseMetaType> MT metaGetOperation(Class<MT> theType, RequestDetails theRequestDetails);
DaoMethodOutcome patch(IIdType theId, PatchTypeEnum thePatchType, String thePatchBody, RequestDetails theRequestDetails);
Set<Long> processMatchUrl(String theMatchUrl); Set<Long> processMatchUrl(String theMatchUrl);
/** /**
@ -181,6 +185,15 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
Set<Long> searchForIdsWithAndOr(SearchParameterMap theParams); Set<Long> searchForIdsWithAndOr(SearchParameterMap theParams);
/**
* Takes a map of incoming raw search parameters and translates/parses them into
* appropriate {@link IQueryParameterType} instances of the appropriate type
* for the given param
*
* @throws InvalidRequestException If any of the parameters are not known
*/
void translateRawParameters(Map<String, List<String>> theSource, SearchParameterMap theTarget);
/** /**
* Update a resource - Note that this variant of the method does not take in a {@link RequestDetails} and * Update a resource - Note that this variant of the method does not take in a {@link RequestDetails} and
* therefore can not fire any interceptors. Use only for internal system calls * therefore can not fire any interceptors. Use only for internal system calls
@ -211,8 +224,6 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
*/ */
MethodOutcome validate(T theResource, IIdType theId, String theRawResource, EncodingEnum theEncoding, ValidationModeEnum theMode, String theProfile, RequestDetails theRequestDetails); MethodOutcome validate(T theResource, IIdType theId, String theRawResource, EncodingEnum theEncoding, ValidationModeEnum theMode, String theProfile, RequestDetails theRequestDetails);
DaoMethodOutcome patch(IIdType theId, PatchTypeEnum thePatchType, String thePatchBody, RequestDetails theRequestDetails);
// /** // /**
// * Invoke the everything operation // * Invoke the everything operation
// */ // */

View File

@ -0,0 +1,16 @@
package ca.uhn.fhir.jpa.dao;
import java.util.Collection;
import java.util.Map;
import ca.uhn.fhir.context.RuntimeSearchParam;
public interface ISearchParamRegistry {
Map<String,RuntimeSearchParam> getActiveSearchParams(String theResourceName);
Collection<RuntimeSearchParam> getAllSearchParams(String theResourceName);
void forceRefresh();
}

View File

@ -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.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionCallback;
@ -95,10 +96,12 @@ public class SearchBuilder {
private IFulltextSearchSvc mySearchDao; private IFulltextSearchSvc mySearchDao;
private Search mySearchEntity; private Search mySearchEntity;
private ISearchResultDao mySearchResultDao; private ISearchResultDao mySearchResultDao;
private ISearchParamRegistry mySerarchParamRegistry;
private IHapiTerminologySvc myTerminologySvc; private IHapiTerminologySvc myTerminologySvc;
public SearchBuilder(FhirContext theFhirContext, EntityManager theEntityManager, PlatformTransactionManager thePlatformTransactionManager, IFulltextSearchSvc theSearchDao, ISearchResultDao theSearchResultDao, BaseHapiFhirDao<?> theDao, 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; myContext = theFhirContext;
myEntityManager = theEntityManager; myEntityManager = theEntityManager;
myPlatformTransactionManager = thePlatformTransactionManager; myPlatformTransactionManager = thePlatformTransactionManager;
@ -108,6 +111,7 @@ public class SearchBuilder {
myResourceIndexedSearchParamUriDao = theResourceIndexedSearchParamUriDao; myResourceIndexedSearchParamUriDao = theResourceIndexedSearchParamUriDao;
myForcedIdDao = theForcedIdDao; myForcedIdDao = theForcedIdDao;
myTerminologySvc = theTerminologySvc; myTerminologySvc = theTerminologySvc;
mySerarchParamRegistry = theSearchParamRegistry;
} }
private void addPredicateComposite(RuntimeSearchParam theParamDef, List<? extends IQueryParameterType> theNextAnd) { private void addPredicateComposite(RuntimeSearchParam theParamDef, List<? extends IQueryParameterType> theNextAnd) {
@ -212,12 +216,13 @@ public class SearchBuilder {
throw new InvalidRequestException("Invalid resource type: " + targetResourceType); throw new InvalidRequestException("Invalid resource type: " + targetResourceType);
} }
RuntimeSearchParam owningParameterDef = targetResourceDefinition.getSearchParam(parameterName.replaceAll("\\..*", "")); String paramName = parameterName.replaceAll("\\..*", "");
RuntimeSearchParam owningParameterDef = myCallingDao.getSearchParamByName(targetResourceDefinition, paramName);
if (owningParameterDef == null) { if (owningParameterDef == null) {
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + parameterName); throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + parameterName);
} }
owningParameterDef = targetResourceDefinition.getSearchParam(owningParameter); owningParameterDef = myCallingDao.getSearchParamByName(targetResourceDefinition, owningParameter);
if (owningParameterDef == null) { if (owningParameterDef == null) {
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + owningParameter); throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + owningParameter);
} }
@ -237,7 +242,7 @@ public class SearchBuilder {
List<Predicate> predicates = new ArrayList<Predicate>(); List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(builder.equal(from.get("mySourceResourceType"), targetResourceType)); predicates.add(builder.equal(from.get("mySourceResourceType"), targetResourceType));
predicates.add(from.get("mySourceResourcePid").in(match)); predicates.add(from.get("mySourceResourcePid").in(match));
predicates.add(createResourceLinkPathPredicate(myContext, owningParameter, from, resourceType)); predicates.add(createResourceLinkPathPredicate(myCallingDao, myContext, owningParameter, from, resourceType));
predicates.add(builder.equal(from.get("myTargetResourceType"), myResourceName)); predicates.add(builder.equal(from.get("myTargetResourceType"), myResourceName));
createPredicateResourceId(builder, cq, predicates, from.get("myId").as(Long.class)); createPredicateResourceId(builder, cq, predicates, from.get("myId").as(Long.class));
createPredicateLastUpdatedForResourceLink(builder, from, predicates); createPredicateLastUpdatedForResourceLink(builder, from, predicates);
@ -548,7 +553,8 @@ public class SearchBuilder {
String resourceId; String resourceId;
if (!ref.getValue().matches("[a-zA-Z]+\\/.*")) { if (!ref.getValue().matches("[a-zA-Z]+\\/.*")) {
String paramPath = myContext.getResourceDefinition(myResourceType).getSearchParam(theParamName).getPath(); RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceType);
String paramPath = myCallingDao.getSearchParamByName(resourceDef, theParamName).getPath();
if (paramPath.endsWith(".as(Reference)")) { if (paramPath.endsWith(".as(Reference)")) {
paramPath = paramPath.substring(0, paramPath.length() - ".as(Reference)".length()) + "Reference"; paramPath = paramPath.substring(0, paramPath.length() - ".as(Reference)".length()) + "Reference";
} }
@ -602,7 +608,7 @@ public class SearchBuilder {
boolean isMeta = BaseHapiFhirDao.RESOURCE_META_PARAMS.containsKey(chain); boolean isMeta = BaseHapiFhirDao.RESOURCE_META_PARAMS.containsKey(chain);
RuntimeSearchParam param = null; RuntimeSearchParam param = null;
if (!isMeta) { if (!isMeta) {
param = typeDef.getSearchParam(chain); param = myCallingDao.getSearchParamByName(typeDef, chain);
if (param == null) { if (param == null) {
ourLog.debug("Type {} doesn't have search param {}", nextType.getSimpleName(), param); ourLog.debug("Type {} doesn't have search param {}", nextType.getSimpleName(), param);
continue; continue;
@ -1380,43 +1386,8 @@ public class SearchBuilder {
return singleCode; return singleCode;
} }
private String determineSystemIfMissing(String theParamName, String code, String system) {
if (system == null) {
RuntimeSearchParam param = getSearchParam(theParamName);
if (param != null) {
Set<String> 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<VersionIndependentConcept> 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<? extends ResourceLink> from) { private Predicate createResourceLinkPathPredicate(String theParamName, Root<? extends ResourceLink> from) {
return createResourceLinkPathPredicate(myContext, theParamName, from, myResourceType); return createResourceLinkPathPredicate(myCallingDao, myContext, theParamName, from, myResourceType);
}
private static Predicate createResourceLinkPathPredicate(FhirContext theContext, String theParamName, Root<? extends ResourceLink> from, Class<? extends IBaseResource> resourceType) {
RuntimeSearchParam param = theContext.getResourceDefinition(resourceType).getSearchParam(theParamName);
List<String> path = param.getPathsSplit();
Predicate type = from.get("mySourcePath").in(path);
return type;
} }
private TypedQuery<Long> createSearchAllByTypeQuery(DateRangeParam theLastUpdated) { private TypedQuery<Long> createSearchAllByTypeQuery(DateRangeParam theLastUpdated) {
@ -1468,7 +1439,8 @@ public class SearchBuilder {
return; return;
} }
RuntimeSearchParam param = getSearchParam(theSort.getParamName()); RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceName);
RuntimeSearchParam param = myCallingDao.getSearchParamByName(resourceDef, theSort.getParamName());
if (param == null) { if (param == null) {
throw new InvalidRequestException("Unknown sort parameter '" + theSort.getParamName() + "'"); throw new InvalidRequestException("Unknown sort parameter '" + theSort.getParamName() + "'");
} }
@ -1532,10 +1504,33 @@ public class SearchBuilder {
createSort(theBuilder, theFrom, theSort.getChain(), theOrders, thePredicates); createSort(theBuilder, theFrom, theSort.getChain(), theOrders, thePredicates);
} }
private RuntimeSearchParam getSearchParam(String theParamName) { private String determineSystemIfMissing(String theParamName, String code, String system) {
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceType); if (system == null) {
RuntimeSearchParam param = resourceDef.getSearchParam(theParamName); RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceName);
return param; RuntimeSearchParam param = myCallingDao.getSearchParamByName(resourceDef, theParamName);
if (param != null) {
Set<String> 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<VersionIndependentConcept> candidateCodes = myTerminologySvc.expandValueSet(valueSetUris.iterator().next());
for (VersionIndependentConcept nextCandidate : candidateCodes) {
if (nextCandidate.getCode().equals(code)) {
system = nextCandidate.getSystem();
break;
}
}
}
}
}
return system;
} }
public Set<Long> doGetPids() { public Set<Long> doGetPids() {
@ -1820,7 +1815,8 @@ public class SearchBuilder {
doInitializeSearch(); doInitializeSearch();
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceType); // RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceType);
Map<String, RuntimeSearchParam> searchParams = mySerarchParamRegistry.getActiveSearchParams(myResourceName);
for (Entry<String, List<List<? extends IQueryParameterType>>> nextParamEntry : params.entrySet()) { for (Entry<String, List<List<? extends IQueryParameterType>>> nextParamEntry : params.entrySet()) {
String nextParamName = nextParamEntry.getKey(); String nextParamName = nextParamEntry.getKey();
@ -1875,7 +1871,7 @@ public class SearchBuilder {
} else { } else {
RuntimeSearchParam nextParamDef = resourceDef.getSearchParam(nextParamName); RuntimeSearchParam nextParamDef = searchParams.get(nextParamName);
if (nextParamDef != null) { if (nextParamDef != null) {
switch (nextParamDef.getParamType()) { switch (nextParamDef.getParamType()) {
case DATE: case DATE:
@ -1953,7 +1949,7 @@ public class SearchBuilder {
} }
} }
public void setType(Class<? extends IBaseResource> theResourceType, String theResourceName) { public void setType(Class<? extends IBaseResource> theResourceType, String theResourceName) {
myResourceType = theResourceType; myResourceType = theResourceType;
myResourceName = theResourceName; myResourceName = theResourceName;
@ -2044,6 +2040,14 @@ public class SearchBuilder {
return likeExpression.replace("%", "[%]") + "%"; return likeExpression.replace("%", "[%]") + "%";
} }
private static Predicate createResourceLinkPathPredicate(IDao theCallingDao, FhirContext theContext, String theParamName, Root<? extends ResourceLink> from, Class<? extends IBaseResource> resourceType) {
RuntimeResourceDefinition resourceDef = theContext.getResourceDefinition(resourceType);
RuntimeSearchParam param = theCallingDao.getSearchParamByName(resourceDef, theParamName);
List<String> path = param.getPathsSplit();
Predicate type = from.get("mySourcePath").in(path);
return type;
}
private static List<Long> filterResourceIdsByLastUpdated(EntityManager theEntityManager, final DateRangeParam theLastUpdated, Collection<Long> thePids) { private static List<Long> filterResourceIdsByLastUpdated(EntityManager theEntityManager, final DateRangeParam theLastUpdated, Collection<Long> thePids) {
CriteriaBuilder builder = theEntityManager.getCriteriaBuilder(); CriteriaBuilder builder = theEntityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = builder.createQuery(Long.class); CriteriaQuery<Long> cq = builder.createQuery(Long.class);
@ -2109,7 +2113,7 @@ public class SearchBuilder {
* *
* @param theLastUpdated * @param theLastUpdated
*/ */
public static HashSet<Long> loadReverseIncludes(FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes, boolean theReverseMode, DateRangeParam theLastUpdated) { public static HashSet<Long> loadReverseIncludes(IDao theCallingDao, FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes, boolean theReverseMode, DateRangeParam theLastUpdated) {
if (theMatches.size() == 0) { if (theMatches.size() == 0) {
return new HashSet<Long>(); return new HashSet<Long>();
} }
@ -2177,7 +2181,11 @@ public class SearchBuilder {
} }
String paramName = nextInclude.getParamName(); String paramName = nextInclude.getParamName();
param = isNotBlank(paramName) ? def.getSearchParam(paramName) : null; if (isNotBlank(paramName)) {
param = theCallingDao.getSearchParamByName(def, paramName);
} else {
param = null;
}
if (param == null) { if (param == null) {
ourLog.warn("Unknown param name in include/revinclude=" + nextInclude.getValue()); ourLog.warn("Unknown param name in include/revinclude=" + nextInclude.getValue());
continue; continue;
@ -2278,9 +2286,9 @@ public class SearchBuilder {
Set<Long> revIncludedPids = new HashSet<Long>(); Set<Long> revIncludedPids = new HashSet<Long>();
if (myParams.getEverythingMode() == null) { if (myParams.getEverythingMode() == null) {
revIncludedPids.addAll(loadReverseIncludes(myContext, myEntityManager, pidsSubList, myParams.getRevIncludes(), true, myParams.getLastUpdated())); revIncludedPids.addAll(loadReverseIncludes(myCallingDao, myContext, myEntityManager, pidsSubList, myParams.getRevIncludes(), true, myParams.getLastUpdated()));
} }
revIncludedPids.addAll(loadReverseIncludes(myContext, myEntityManager, pidsSubList, myParams.getIncludes(), false, myParams.getLastUpdated())); revIncludedPids.addAll(loadReverseIncludes(myCallingDao, myContext, myEntityManager, pidsSubList, myParams.getIncludes(), false, myParams.getLastUpdated()));
// Execute the query and make sure we return distinct results // Execute the query and make sure we return distinct results
List<IBaseResource> resources = new ArrayList<IBaseResource>(); List<IBaseResource> resources = new ArrayList<IBaseResource>();

View File

@ -25,6 +25,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -38,8 +39,6 @@ import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.ConfigurationException; 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.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords;
@ -79,8 +78,8 @@ public class SearchParamExtractorDstu1 extends BaseSearchParamExtractor implemen
public Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) { public Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamDate> retVal = new HashSet<ResourceIndexedSearchParamDate>(); HashSet<ResourceIndexedSearchParamDate> retVal = new HashSet<ResourceIndexedSearchParamDate>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.DATE) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.DATE) {
continue; continue;
} }
@ -130,12 +129,13 @@ public class SearchParamExtractorDstu1 extends BaseSearchParamExtractor implemen
return retVal; return retVal;
} }
@Override @Override
public HashSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) { public HashSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamNumber> retVal = new HashSet<ResourceIndexedSearchParamNumber>(); HashSet<ResourceIndexedSearchParamNumber> retVal = new HashSet<ResourceIndexedSearchParamNumber>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.NUMBER) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.NUMBER) {
continue; continue;
} }
@ -229,8 +229,8 @@ public class SearchParamExtractorDstu1 extends BaseSearchParamExtractor implemen
public Set<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) { public Set<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamQuantity> retVal = new HashSet<ResourceIndexedSearchParamQuantity>(); HashSet<ResourceIndexedSearchParamQuantity> retVal = new HashSet<ResourceIndexedSearchParamQuantity>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.QUANTITY) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.QUANTITY) {
continue; continue;
} }
@ -277,8 +277,8 @@ public class SearchParamExtractorDstu1 extends BaseSearchParamExtractor implemen
public Set<ResourceIndexedSearchParamString> extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) { public Set<ResourceIndexedSearchParamString> extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamString> retVal = new HashSet<ResourceIndexedSearchParamString>(); HashSet<ResourceIndexedSearchParamString> retVal = new HashSet<ResourceIndexedSearchParamString>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.STRING) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.STRING) {
continue; continue;
} }
@ -363,8 +363,8 @@ public class SearchParamExtractorDstu1 extends BaseSearchParamExtractor implemen
public Set<BaseResourceIndexedSearchParam> extractSearchParamTokens(ResourceTable theEntity, IBaseResource theResource) { public Set<BaseResourceIndexedSearchParam> extractSearchParamTokens(ResourceTable theEntity, IBaseResource theResource) {
HashSet<BaseResourceIndexedSearchParam> retVal = new HashSet<BaseResourceIndexedSearchParam>(); HashSet<BaseResourceIndexedSearchParam> retVal = new HashSet<BaseResourceIndexedSearchParam>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.TOKEN) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.TOKEN) {
continue; continue;
} }

View File

@ -24,6 +24,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -120,8 +121,8 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
public Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) { public Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamDate> retVal = new HashSet<ResourceIndexedSearchParamDate>(); HashSet<ResourceIndexedSearchParamDate> retVal = new HashSet<ResourceIndexedSearchParamDate>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.DATE) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.DATE) {
continue; continue;
} }
@ -181,8 +182,8 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
public HashSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) { public HashSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamNumber> retVal = new HashSet<ResourceIndexedSearchParamNumber>(); HashSet<ResourceIndexedSearchParamNumber> retVal = new HashSet<ResourceIndexedSearchParamNumber>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.NUMBER) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.NUMBER) {
continue; continue;
} }
@ -281,8 +282,8 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
public Set<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) { public Set<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamQuantity> retVal = new HashSet<ResourceIndexedSearchParamQuantity>(); HashSet<ResourceIndexedSearchParamQuantity> retVal = new HashSet<ResourceIndexedSearchParamQuantity>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.QUANTITY) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.QUANTITY) {
continue; continue;
} }
@ -335,23 +336,25 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
public Set<ResourceIndexedSearchParamString> extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) { public Set<ResourceIndexedSearchParamString> extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamString> retVal = new HashSet<ResourceIndexedSearchParamString>(); HashSet<ResourceIndexedSearchParamString> retVal = new HashSet<ResourceIndexedSearchParamString>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); String resourceName = getContext().getResourceDefinition(theResource).getName();
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) {
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.STRING) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.STRING) {
continue; continue;
} }
String nextPath = nextSpDef.getPath(); String nextPath = nextSpDef.getPath();
String resourceName = nextSpDef.getName(); String nextSpName = nextSpDef.getName();
if (isBlank(nextPath)) { if (isBlank(nextPath)) {
// TODO: implement phonetic, and any others that have no path // 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; Questionnaire q = (Questionnaire) theResource;
String title = q.getGroup().getTitle(); String title = q.getGroup().getTitle();
addSearchTerm(theEntity, retVal, resourceName, title); addSearchTerm(theEntity, retVal, nextSpName, title);
} }
continue; continue;
} }
@ -369,7 +372,7 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
if (nextObject instanceof IPrimitiveDatatype<?>) { if (nextObject instanceof IPrimitiveDatatype<?>) {
IPrimitiveDatatype<?> nextValue = (IPrimitiveDatatype<?>) nextObject; IPrimitiveDatatype<?> nextValue = (IPrimitiveDatatype<?>) nextObject;
String searchTerm = nextValue.getValueAsString(); String searchTerm = nextValue.getValueAsString();
addSearchTerm(theEntity, retVal, resourceName, searchTerm); addSearchTerm(theEntity, retVal, nextSpName, searchTerm);
} else { } else {
if (nextObject instanceof BaseHumanNameDt) { if (nextObject instanceof BaseHumanNameDt) {
ArrayList<StringDt> allNames = new ArrayList<StringDt>(); ArrayList<StringDt> allNames = new ArrayList<StringDt>();
@ -377,7 +380,7 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
allNames.addAll(nextHumanName.getFamily()); allNames.addAll(nextHumanName.getFamily());
allNames.addAll(nextHumanName.getGiven()); allNames.addAll(nextHumanName.getGiven());
for (StringDt nextName : allNames) { for (StringDt nextName : allNames) {
addSearchTerm(theEntity, retVal, resourceName, nextName.getValue()); addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue());
} }
} else if (nextObject instanceof AddressDt) { } else if (nextObject instanceof AddressDt) {
ArrayList<StringDt> allNames = new ArrayList<StringDt>(); ArrayList<StringDt> allNames = new ArrayList<StringDt>();
@ -388,16 +391,16 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
allNames.add(nextAddress.getCountryElement()); allNames.add(nextAddress.getCountryElement());
allNames.add(nextAddress.getPostalCodeElement()); allNames.add(nextAddress.getPostalCodeElement());
for (StringDt nextName : allNames) { for (StringDt nextName : allNames) {
addSearchTerm(theEntity, retVal, resourceName, nextName.getValue()); addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue());
} }
} else if (nextObject instanceof ContactPointDt) { } else if (nextObject instanceof ContactPointDt) {
ContactPointDt nextContact = (ContactPointDt) nextObject; ContactPointDt nextContact = (ContactPointDt) nextObject;
if (nextContact.getValueElement().isEmpty() == false) { if (nextContact.getValueElement().isEmpty() == false) {
addSearchTerm(theEntity, retVal, resourceName, nextContact.getValue()); addSearchTerm(theEntity, retVal, nextSpName, nextContact.getValue());
} }
} else { } else {
if (!multiType) { 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(); useSystem = vs.getCodeSystem().getSystem();
} }
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.TOKEN) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.TOKEN) {
continue; continue;
} }
@ -575,8 +578,8 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
public Set<ResourceIndexedSearchParamUri> extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) { public Set<ResourceIndexedSearchParamUri> extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamUri> retVal = new HashSet<ResourceIndexedSearchParamUri>(); HashSet<ResourceIndexedSearchParamUri> retVal = new HashSet<ResourceIndexedSearchParamUri>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.URI) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.URI) {
continue; continue;
} }
@ -622,6 +625,7 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
return retVal; return retVal;
} }
private void extractTokensFromCodeableConcept(List<String> theSystems, List<String> theCodes, CodeableConceptDt theCodeableConcept, ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> theListToPopulate, RuntimeSearchParam theParameterDef) { private void extractTokensFromCodeableConcept(List<String> theSystems, List<String> theCodes, CodeableConceptDt theCodeableConcept, ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> theListToPopulate, RuntimeSearchParam theParameterDef) {
for (CodingDt nextCoding : theCodeableConcept.getCoding()) { for (CodingDt nextCoding : theCodeableConcept.getCoding()) {
extractTokensFromCoding(theSystems, theCodes, theEntity, theListToPopulate, theParameterDef, nextCoding); extractTokensFromCoding(theSystems, theCodes, theEntity, theListToPopulate, theParameterDef, nextCoding);

View File

@ -0,0 +1,6 @@
package ca.uhn.fhir.jpa.dao;
public class SearchParamRegistryDstu1 extends BaseSearchParamRegistry {
// nothing yet
}

View File

@ -0,0 +1,6 @@
package ca.uhn.fhir.jpa.dao;
public class SearchParamRegistryDstu2 extends BaseSearchParamRegistry {
// nothing yet
}

View File

@ -10,7 +10,7 @@ package ca.uhn.fhir.jpa.dao.data;
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * 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 * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -21,9 +21,16 @@ package ca.uhn.fhir.jpa.dao.data;
*/ */
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.ResourceTable;
public interface IResourceTableDao extends JpaRepository<ResourceTable, Long> { public interface IResourceTableDao extends JpaRepository<ResourceTable, Long> {
// nothing yet
@Modifying
@Query("UPDATE ResourceTable r SET r.myIndexStatus = null WHERE r.myResourceType = :restype")
int markResourcesOfTypeAsRequiringReindexing(@Param("restype") String theResourceType);
} }

View File

@ -92,7 +92,8 @@ public class FhirResourceDaoDstu3<T extends IAnyResource> extends BaseHapiFhirRe
values.addAll(theTerser.getAllPopulatedChildElementsOfType(theResource, BaseResourceReferenceDt.class)); values.addAll(theTerser.getAllPopulatedChildElementsOfType(theResource, BaseResourceReferenceDt.class));
} else if (theInclude.getValue().startsWith(theResourceDef.getName() + ":")) { } else if (theInclude.getValue().startsWith(theResourceDef.getName() + ":")) {
values = new ArrayList<Object>(); values = new ArrayList<Object>();
RuntimeSearchParam sp = theResourceDef.getSearchParam(theInclude.getValue().substring(theInclude.getValue().indexOf(':') + 1)); String paramName = theInclude.getValue().substring(theInclude.getValue().indexOf(':') + 1);
RuntimeSearchParam sp = getSearchParamByName(theResourceDef, paramName);
for (String nextPath : sp.getPathsSplit()) { for (String nextPath : sp.getPathsSplit()) {
values.addAll(theTerser.getValues(theResource, nextPath)); values.addAll(theTerser.getValues(theResource, nextPath));
} }

View File

@ -27,8 +27,10 @@ import javax.servlet.http.HttpServletRequest;
import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType; 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.IFhirResourceDaoPatient;
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchParameterMap; import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.dao.SearchParameterMap.EverythingModeEnum; 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 FhirResourceDaoDstu3<Patient>implements IFhirResourceDaoPatient<Patient> { public class FhirResourceDaoPatientDstu3 extends FhirResourceDaoDstu3<Patient>implements IFhirResourceDaoPatient<Patient> {
@Autowired
private ISearchParamRegistry mySerarchParamRegistry;
private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative) { private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative) {
SearchParameterMap paramMap = new SearchParameterMap(); SearchParameterMap paramMap = new SearchParameterMap();
if (theCount != null) { if (theCount != null) {
@ -64,7 +69,7 @@ public class FhirResourceDaoPatientDstu3 extends FhirResourceDaoDstu3<Patient>im
paramMap.add("_id", new StringParam(theId.getIdPart())); 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()); builder.setType(getResourceType(), getResourceName());
return builder.search(paramMap); return builder.search(paramMap);
} }

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.jpa.dao.dstu3; package ca.uhn.fhir.jpa.dao.dstu3;
import static org.apache.commons.lang3.StringUtils.isBlank;
/* /*
* #%L * #%L
* HAPI FHIR JPA Server * HAPI FHIR JPA Server
@ -10,7 +12,7 @@ package ca.uhn.fhir.jpa.dao.dstu3;
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * 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 * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -27,22 +29,37 @@ import org.hl7.fhir.dstu3.model.SearchParameter;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import ca.uhn.fhir.jpa.dao.BaseSearchParamExtractor;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSearchParameter; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSearchParameter;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
public class FhirResourceDaoSearchParameterDstu3 extends FhirResourceDaoDstu3<SearchParameter>implements IFhirResourceDaoSearchParameter<SearchParameter> { public class FhirResourceDaoSearchParameterDstu3 extends FhirResourceDaoDstu3<SearchParameter> implements IFhirResourceDaoSearchParameter<SearchParameter> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSearchParameterDstu3.class);
@Autowired @Autowired
private IFhirSystemDao<Bundle, Meta> mySystemDao; private IFhirSystemDao<Bundle, Meta> mySystemDao;
private void markAffectedResources(SearchParameter theResource) {
String xpath = theResource.getXpath();
String resourceType = xpath.substring(0, xpath.indexOf('.'));
ourLog.info("Marking all resources of type {} for reindexing due to updated search parameter with path: {}", xpath);
int updatedCount = myResourceTableDao.markResourcesOfTypeAsRequiringReindexing(resourceType);
ourLog.info("Marked {} resources for reindexing", updatedCount);
}
/** /**
* This method is called once per minute to perform any required re-indexing. During most passes this will * This method is called once per minute to perform any required re-indexing. During most passes this will
* just check and find that there are no resources requiring re-indexing. In that case the method just returns * just check and find that there are no resources requiring re-indexing. In that case the method just returns
* immediately. If the search finds that some resources require reindexing, the system will do a bunch of * immediately. If the search finds that some resources require reindexing, the system will do multiple
* reindexing and then return. * reindexing passes and then return.
*/ */
@Override @Override
@Scheduled(fixedDelay=DateUtils.MILLIS_PER_MINUTE) @Scheduled(fixedDelay = DateUtils.MILLIS_PER_MINUTE)
public void performReindexingPass() { public void performReindexingPass() {
if (getConfig().isSchedulingDisabled()) { if (getConfig().isSchedulingDisabled()) {
return; return;
@ -57,7 +74,57 @@ public class FhirResourceDaoSearchParameterDstu3 extends FhirResourceDaoDstu3<Se
break; break;
} }
} }
} }
@Override
protected void postPersist(ResourceTable theEntity, SearchParameter theResource) {
markAffectedResources(theResource);
}
@Override
protected void postUpdate(ResourceTable theEntity, SearchParameter theResource) {
markAffectedResources(theResource);
}
@Override
protected void validateResourceForStorage(SearchParameter theResource, ResourceTable theEntityToSave) {
super.validateResourceForStorage(theResource, theEntityToSave);
if (theResource.getStatus() == null) {
throw new UnprocessableEntityException("Resource.status is missing or invalid: " + theResource.getStatusElement().getValueAsString());
}
String xpath = theResource.getXpath();
if (isBlank(xpath)) {
throw new UnprocessableEntityException("Resource.xpath is missing");
}
String[] xpathSplit = BaseSearchParamExtractor.SPLIT.split(xpath);
String allResourceName = null;
for (String nextPath : xpathSplit) {
int dotIdx = nextPath.indexOf('.');
if (dotIdx == -1) {
throw new UnprocessableEntityException("Invalid path value \"" + nextPath + "\". Must start with a resource name");
}
String resourceName = nextPath.substring(0, dotIdx);
try {
getContext().getResourceDefinition(resourceName);
} catch (DataFormatException e) {
throw new UnprocessableEntityException("Invalid path value \"" + nextPath + "\": " + e.getMessage());
}
if (allResourceName == null) {
allResourceName = resourceName;
} else {
if (!allResourceName.equals(resourceName)) {
throw new UnprocessableEntityException("Invalid path value \"" + nextPath + "\". All paths in a single SearchParameter must match the same resource type");
}
}
}
}
} }

View File

@ -157,7 +157,7 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
private int pollForNewUndeliveredResources(SubscriptionTable theSubscriptionTable) { private int pollForNewUndeliveredResources(SubscriptionTable theSubscriptionTable) {
Subscription subscription = toResource(Subscription.class, theSubscriptionTable.getSubscriptionResource(), false); Subscription subscription = toResource(Subscription.class, theSubscriptionTable.getSubscriptionResource(), false);
RuntimeResourceDefinition resourceDef = validateCriteriaAndReturnResourceDefinition(subscription); RuntimeResourceDefinition resourceDef = validateCriteriaAndReturnResourceDefinition(subscription);
SearchParameterMap criteriaUrl = translateMatchUrl(getContext(), subscription.getCriteria(), resourceDef); SearchParameterMap criteriaUrl = translateMatchUrl(this, getContext(), subscription.getCriteria(), resourceDef);
criteriaUrl = new SearchParameterMap(); criteriaUrl = new SearchParameterMap();
long start = theSubscriptionTable.getMostRecentMatch().getTime(); long start = theSubscriptionTable.getMostRecentMatch().getTime();

View File

@ -24,6 +24,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashSet; 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.Patient.PatientCommunicationComponent;
import org.hl7.fhir.dstu3.model.Period; import org.hl7.fhir.dstu3.model.Period;
import org.hl7.fhir.dstu3.model.Quantity; 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.Range;
import org.hl7.fhir.dstu3.model.SimpleQuantity; import org.hl7.fhir.dstu3.model.SimpleQuantity;
import org.hl7.fhir.dstu3.model.StringType; 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.ConfigurationException;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.BaseSearchParamExtractor; import ca.uhn.fhir.jpa.dao.BaseSearchParamExtractor;
import ca.uhn.fhir.jpa.dao.ISearchParamExtractor; 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.dao.PathAndRef;
import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords;
@ -97,7 +97,7 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
@Autowired @Autowired
private org.hl7.fhir.dstu3.hapi.validation.IValidationSupport myValidationSupport; private org.hl7.fhir.dstu3.hapi.validation.IValidationSupport myValidationSupport;
/** /**
* Constructor * Constructor
*/ */
@ -105,8 +105,8 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
super(); super();
} }
public SearchParamExtractorDstu3(FhirContext theCtx, IValidationSupport theValidationSupport) { public SearchParamExtractorDstu3(FhirContext theCtx, IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry) {
super(theCtx); super(theCtx, theSearchParamRegistry);
myValidationSupport = theValidationSupport; myValidationSupport = theValidationSupport;
} }
@ -147,8 +147,8 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
public Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) { public Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamDate> retVal = new HashSet<ResourceIndexedSearchParamDate>(); HashSet<ResourceIndexedSearchParamDate> retVal = new HashSet<ResourceIndexedSearchParamDate>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.DATE) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.DATE) {
continue; continue;
} }
@ -226,8 +226,8 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
public HashSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) { public HashSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamNumber> retVal = new HashSet<ResourceIndexedSearchParamNumber>(); HashSet<ResourceIndexedSearchParamNumber> retVal = new HashSet<ResourceIndexedSearchParamNumber>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.NUMBER) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.NUMBER) {
continue; continue;
} }
@ -320,8 +320,8 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
public Set<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) { public Set<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamQuantity> retVal = new HashSet<ResourceIndexedSearchParamQuantity>(); HashSet<ResourceIndexedSearchParamQuantity> retVal = new HashSet<ResourceIndexedSearchParamQuantity>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.QUANTITY) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.QUANTITY) {
continue; continue;
} }
@ -384,24 +384,28 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
public Set<ResourceIndexedSearchParamString> extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) { public Set<ResourceIndexedSearchParamString> extractSearchParamStrings(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamString> retVal = new HashSet<ResourceIndexedSearchParamString>(); HashSet<ResourceIndexedSearchParamString> retVal = new HashSet<ResourceIndexedSearchParamString>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); String resourceName = getContext().getResourceDefinition(theResource).getName();
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) {
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.STRING) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.STRING) {
continue; continue;
} }
String nextPath = nextSpDef.getPath(); String nextPath = nextSpDef.getPath();
String resourceName = nextSpDef.getName(); String nextSpName = nextSpDef.getName();
if (isBlank(nextPath)) { if (isBlank(nextPath)) {
// TODO: implement phonetic, and any others that have no path // // TODO: implement phonetic, and any others that have no path
//
if ("Questionnaire".equals(def.getName()) && nextSpDef.getName().equals("title")) { // // TODO: do we still need this check?
Questionnaire q = (Questionnaire) theResource; // if ("Questionnaire".equals(nextSpName) && nextSpDef.getName().equals("title")) {
String title = "";// q.getGroup().getTitle(); // Questionnaire q = (Questionnaire) theResource;
addSearchTerm(theEntity, retVal, resourceName, title); // String title = "";// q.getGroup().getTitle();
} // addSearchTerm(theEntity, retVal, nextSpName, title);
// }
continue; continue;
} }
@ -418,7 +422,7 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
if (nextObject instanceof IPrimitiveType<?>) { if (nextObject instanceof IPrimitiveType<?>) {
IPrimitiveType<?> nextValue = (IPrimitiveType<?>) nextObject; IPrimitiveType<?> nextValue = (IPrimitiveType<?>) nextObject;
String searchTerm = nextValue.getValueAsString(); String searchTerm = nextValue.getValueAsString();
addSearchTerm(theEntity, retVal, resourceName, searchTerm); addSearchTerm(theEntity, retVal, nextSpName, searchTerm);
} else { } else {
if (nextObject instanceof HumanName) { if (nextObject instanceof HumanName) {
ArrayList<StringType> allNames = new ArrayList<StringType>(); ArrayList<StringType> allNames = new ArrayList<StringType>();
@ -428,7 +432,7 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
} }
allNames.addAll(nextHumanName.getGiven()); allNames.addAll(nextHumanName.getGiven());
for (StringType nextName : allNames) { for (StringType nextName : allNames) {
addSearchTerm(theEntity, retVal, resourceName, nextName.getValue()); addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue());
} }
} else if (nextObject instanceof Address) { } else if (nextObject instanceof Address) {
ArrayList<StringType> allNames = new ArrayList<StringType>(); ArrayList<StringType> allNames = new ArrayList<StringType>();
@ -439,29 +443,29 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
allNames.add(nextAddress.getCountryElement()); allNames.add(nextAddress.getCountryElement());
allNames.add(nextAddress.getPostalCodeElement()); allNames.add(nextAddress.getPostalCodeElement());
for (StringType nextName : allNames) { for (StringType nextName : allNames) {
addSearchTerm(theEntity, retVal, resourceName, nextName.getValue()); addSearchTerm(theEntity, retVal, nextSpName, nextName.getValue());
} }
} else if (nextObject instanceof ContactPoint) { } else if (nextObject instanceof ContactPoint) {
ContactPoint nextContact = (ContactPoint) nextObject; ContactPoint nextContact = (ContactPoint) nextObject;
if (nextContact.getValueElement().isEmpty() == false) { if (nextContact.getValueElement().isEmpty() == false) {
addSearchTerm(theEntity, retVal, resourceName, nextContact.getValue()); addSearchTerm(theEntity, retVal, nextSpName, nextContact.getValue());
} }
} else if (nextObject instanceof Quantity) { } else if (nextObject instanceof Quantity) {
BigDecimal value = ((Quantity) nextObject).getValue(); BigDecimal value = ((Quantity) nextObject).getValue();
if (value != null) { if (value != null) {
addSearchTerm(theEntity, retVal, resourceName, value.toPlainString()); addSearchTerm(theEntity, retVal, nextSpName, value.toPlainString());
} }
} else if (nextObject instanceof Range) { } else if (nextObject instanceof Range) {
SimpleQuantity low = ((Range) nextObject).getLow(); SimpleQuantity low = ((Range) nextObject).getLow();
if (low != null) { if (low != null) {
BigDecimal value = low.getValue(); BigDecimal value = low.getValue();
if (value != null) { if (value != null) {
addSearchTerm(theEntity, retVal, resourceName, value.toPlainString()); addSearchTerm(theEntity, retVal, nextSpName, value.toPlainString());
} }
} }
} else { } else {
if (!multiType) { 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(); useSystem = cs.getUrl();
} }
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.TOKEN) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.TOKEN) {
continue; continue;
} }
@ -633,8 +637,8 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
public Set<ResourceIndexedSearchParamUri> extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) { public Set<ResourceIndexedSearchParamUri> extractSearchParamUri(ResourceTable theEntity, IBaseResource theResource) {
HashSet<ResourceIndexedSearchParamUri> retVal = new HashSet<ResourceIndexedSearchParamUri>(); HashSet<ResourceIndexedSearchParamUri> retVal = new HashSet<ResourceIndexedSearchParamUri>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { for (RuntimeSearchParam nextSpDef : searchParams) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.URI) { if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.URI) {
continue; continue;
} }

View File

@ -0,0 +1,205 @@
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<SearchParameter> mySpDao;
private long myLastRefresh;
private volatile Map<String, Map<String, RuntimeSearchParam>> myActiveSearchParams;
@Autowired
private DaoConfig myDaoConfig;
@Override
public Map<String, RuntimeSearchParam> getActiveSearchParams(String theResourceName) {
long refreshInterval = 60 * DateUtils.MILLIS_PER_MINUTE;
if (System.currentTimeMillis() - refreshInterval > myLastRefresh) {
StopWatch sw = new StopWatch();
Map<String, Map<String, RuntimeSearchParam>> searchParams = new HashMap<String, Map<String, RuntimeSearchParam>>();
for (Entry<String, Map<String, RuntimeSearchParam>> 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<IBaseResource> 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<String, RuntimeSearchParam> searchParamMap = getSearchParamMap(searchParams, resourceType);
String name = runtimeSp.getName();
if (myDaoConfig.isDefaultSearchParamsCanBeOverridden() || !searchParamMap.containsKey(name)) {
searchParamMap.put(name, runtimeSp);
}
}
Map<String, Map<String, RuntimeSearchParam>> activeSearchParams = new HashMap<String, Map<String, RuntimeSearchParam>>();
for (Entry<String, Map<String, RuntimeSearchParam>> nextEntry : searchParams.entrySet()) {
for (RuntimeSearchParam nextSp : nextEntry.getValue().values()) {
String nextName = nextSp.getName();
if (nextSp.getStatus() != RuntimeSearchParamStatusEnum.ACTIVE) {
nextSp = null;
}
if (!activeSearchParams.containsKey(nextEntry.getKey())) {
activeSearchParams.put(nextEntry.getKey(), new HashMap<String, RuntimeSearchParam>());
}
if (activeSearchParams.containsKey(nextEntry.getKey())) {
ourLog.info("Replacing existing/built in search param {}:{} with new one", nextEntry.getKey(), nextName);
}
if (nextSp != null) {
activeSearchParams.get(nextEntry.getKey()).put(nextName, nextSp);
} else {
activeSearchParams.get(nextEntry.getKey()).remove(nextName);
}
}
}
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<String, RuntimeSearchParam> getSearchParamMap(Map<String, Map<String, RuntimeSearchParam>> searchParams, String theResourceName) {
Map<String, RuntimeSearchParam> retVal = searchParams.get(theResourceName);
if (retVal == null) {
retVal = new HashMap<String, RuntimeSearchParam>();
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<String> providesMembershipInCompartments = Collections.emptySet();
Set<String> 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<String> toStrings(List<CodeType> theTarget) {
HashSet<String> retVal = new HashSet<String>();
for (CodeType next : theTarget) {
if (isNotBlank(next.getValue())) {
retVal.add(next.getValue());
}
}
return retVal;
}
}

View File

@ -77,6 +77,7 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
@Index(name = "IDX_RES_DATE", columnList="RES_UPDATED"), @Index(name = "IDX_RES_DATE", columnList="RES_UPDATED"),
@Index(name = "IDX_RES_LANG", columnList="RES_TYPE,RES_LANGUAGE"), @Index(name = "IDX_RES_LANG", columnList="RES_TYPE,RES_LANGUAGE"),
@Index(name = "IDX_RES_PROFILE", columnList="RES_PROFILE"), @Index(name = "IDX_RES_PROFILE", columnList="RES_PROFILE"),
@Index(name = "IDX_RES_TYPE", columnList="RES_TYPE"),
@Index(name = "IDX_INDEXSTATUS", columnList="SP_INDEX_STATUS") @Index(name = "IDX_INDEXSTATUS", columnList="SP_INDEX_STATUS")
}) })
@AnalyzerDefs({ @AnalyzerDefs({

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.jpa.provider.dstu3; package ca.uhn.fhir.jpa.provider.dstu3;
import java.util.Collection;
/* /*
* #%L * #%L
* HAPI FHIR JPA Server * HAPI FHIR JPA Server
@ -27,10 +29,15 @@ import javax.servlet.http.HttpServletRequest;
import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.CapabilityStatement.*; import org.hl7.fhir.dstu3.model.CapabilityStatement.*;
import org.hl7.fhir.dstu3.model.Enumerations.SearchParamType; import org.hl7.fhir.dstu3.model.Enumerations.SearchParamType;
import org.springframework.beans.factory.annotation.Autowired;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.ExtensionConstants; import ca.uhn.fhir.util.ExtensionConstants;
@ -62,7 +69,7 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se
myDaoConfig = theDaoConfig; myDaoConfig = theDaoConfig;
super.setCache(false); super.setCache(false);
} }
@Override @Override
public CapabilityStatement getServerConformance(HttpServletRequest theRequest) { public CapabilityStatement getServerConformance(HttpServletRequest theRequest) {
CapabilityStatement retVal = myCachedValue; CapabilityStatement retVal = myCachedValue;
@ -89,19 +96,48 @@ public class JpaConformanceProviderDstu3 extends org.hl7.fhir.dstu3.hapi.rest.se
nextResource.addExtension(new Extension(ExtensionConstants.CONF_RESOURCE_COUNT, new DecimalType(count))); nextResource.addExtension(new Extension(ExtensionConstants.CONF_RESOURCE_COUNT, new DecimalType(count)));
} }
// Add chained params nextResource.getSearchParam().clear();
for (CapabilityStatementRestResourceSearchParamComponent nextParam : nextResource.getSearchParam()) { String resourceName = nextResource.getType();
if (nextParam.getType() == SearchParamType.REFERENCE) { RuntimeResourceDefinition resourceDef = myRestfulServer.getFhirContext().getResourceDefinition(resourceName);
// List<CodeType> targets = nextParam.getTarget(); Collection<RuntimeSearchParam> searchParams = mySystemDao.getSearchParamsByResourceType(resourceDef);
// for (CodeType next : targets) { for (RuntimeSearchParam runtimeSp : searchParams) {
// RuntimeResourceDefinition def = ctx.getResourceDefinition(next.getValue()); CapabilityStatementRestResourceSearchParamComponent confSp = nextResource.addSearchParam();
// for (RuntimeSearchParam nextChainedParam : def.getSearchParams()) {
// nextParam.addChain(nextChainedParam.getName());
// }
// }
}
}
confSp.setName(runtimeSp.getName());
confSp.setDocumentation(runtimeSp.getDescription());
confSp.setDefinition(runtimeSp.getUri());
switch (runtimeSp.getParamType()) {
case COMPOSITE:
confSp.setType(SearchParamType.COMPOSITE);
break;
case DATE:
confSp.setType(SearchParamType.DATE);
break;
case NUMBER:
confSp.setType(SearchParamType.NUMBER);
break;
case QUANTITY:
confSp.setType(SearchParamType.QUANTITY);
break;
case REFERENCE:
confSp.setType(SearchParamType.REFERENCE);
break;
case STRING:
confSp.setType(SearchParamType.STRING);
break;
case TOKEN:
confSp.setType(SearchParamType.TOKEN);
break;
case URI:
confSp.setType(SearchParamType.URI);
break;
case HAS:
// Shouldn't happen
break;
}
}
} }
} }

View File

@ -141,9 +141,9 @@ public final class PersistedJpaBundleProvider implements IBundleProvider {
Set<Long> revIncludedPids = new HashSet<Long>(); Set<Long> revIncludedPids = new HashSet<Long>();
if (mySearchEntity.getSearchType() == SearchTypeEnum.SEARCH) { if (mySearchEntity.getSearchType() == SearchTypeEnum.SEARCH) {
revIncludedPids.addAll(SearchBuilder.loadReverseIncludes(myContext, myEntityManager, pidsSubList, mySearchEntity.toRevIncludesList(), true, mySearchEntity.getLastUpdated())); revIncludedPids.addAll(SearchBuilder.loadReverseIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toRevIncludesList(), true, mySearchEntity.getLastUpdated()));
} }
revIncludedPids.addAll(SearchBuilder.loadReverseIncludes(myContext, myEntityManager, pidsSubList, mySearchEntity.toIncludesList(), false, mySearchEntity.getLastUpdated())); revIncludedPids.addAll(SearchBuilder.loadReverseIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toIncludesList(), false, mySearchEntity.getLastUpdated()));
// Execute the query and make sure we return distinct results // Execute the query and make sure we return distinct results
List<IBaseResource> resources = new ArrayList<IBaseResource>(); List<IBaseResource> resources = new ArrayList<IBaseResource>();

View File

@ -1,11 +1,16 @@
package ca.uhn.fhir.jpa.dao; package ca.uhn.fhir.jpa.dao;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Test; import org.junit.Test;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.dstu2.composite.PeriodDt; import ca.uhn.fhir.model.dstu2.composite.PeriodDt;
import ca.uhn.fhir.model.dstu2.resource.Condition; import ca.uhn.fhir.model.dstu2.resource.Condition;
import ca.uhn.fhir.model.dstu2.resource.Observation; import ca.uhn.fhir.model.dstu2.resource.Observation;
@ -25,7 +30,12 @@ public class BaseHapiFhirDaoTest extends BaseJpaTest {
@Test @Test
public void testTranslateMatchUrl() { public void testTranslateMatchUrl() {
SearchParameterMap match = BaseHapiFhirDao.translateMatchUrl(ourCtx, "Condition?patient=304&_lastUpdated=>2011-01-01T11:12:21.0000Z", ourCtx.getResourceDefinition(Condition.class)); RuntimeResourceDefinition resourceDef = ourCtx.getResourceDefinition(Condition.class);
IDao dao = mock(IDao.class);
when(dao.getSearchParamByName(any(RuntimeResourceDefinition.class), eq("patient"))).thenReturn(resourceDef.getSearchParam("patient"));
SearchParameterMap match = BaseHapiFhirDao.translateMatchUrl(dao, ourCtx, "Condition?patient=304&_lastUpdated=>2011-01-01T11:12:21.0000Z", resourceDef);
assertEquals("2011-01-01T11:12:21.0000Z", match.getLastUpdated().getLowerBound().getValueAsString()); assertEquals("2011-01-01T11:12:21.0000Z", match.getLastUpdated().getLowerBound().getValueAsString());
assertEquals(ReferenceParam.class, match.get("patient").get(0).get(0).getClass()); assertEquals(ReferenceParam.class, match.get("patient").get(0).get(0).getClass());
assertEquals("304", ((ReferenceParam)match.get("patient").get(0).get(0)).getIdPart()); assertEquals("304", ((ReferenceParam)match.get("patient").get(0).get(0)).getIdPart());

View File

@ -53,6 +53,12 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
private static JpaValidationSupportChainDstu3 ourJpaValidationSupportChainDstu3; private static JpaValidationSupportChainDstu3 ourJpaValidationSupportChainDstu3;
private static IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> ourValueSetDao; private static IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> ourValueSetDao;
@Autowired
@Qualifier("mySearchParameterDaoDstu3")
protected IFhirResourceDao<SearchParameter> mySearchParameterDao;
@Autowired
protected ISearchParamRegistry mySearchParamRegsitry;
// @Autowired // @Autowired
// protected HapiWorkerContext myHapiWorkerContext; // protected HapiWorkerContext myHapiWorkerContext;
@Autowired @Autowired

View File

@ -0,0 +1,202 @@
package ca.uhn.fhir.jpa.dao.dstu3;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.empty;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.util.List;
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.SearchParameter;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.AfterClass;
import org.junit.Test;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3SearchCustomSearchParamTest.class);
@Test
public void testCreateInvalidParamInvalidResourceName() {
SearchParameter fooSp = new SearchParameter();
fooSp.setCode("foo");
fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN);
fooSp.setTitle("FOO SP");
fooSp.setXpath("PatientFoo.gender");
fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL);
fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
try {
mySearchParameterDao.create(fooSp, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertEquals("Invalid path value \"PatientFoo.gender\": Unknown resource name \"PatientFoo\" (this name is not known in FHIR version \"DSTU3\")", e.getMessage());
}
}
@Test
public void testCreateInvalidParamMismatchedResourceName() {
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 or Observation.code");
fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL);
fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
try {
mySearchParameterDao.create(fooSp, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertEquals("Invalid path value \"Observation.code\". All paths in a single SearchParameter must match the same resource type", e.getMessage());
}
}
@Test
public void testCreateInvalidParamNoPath() {
SearchParameter fooSp = new SearchParameter();
fooSp.setCode("foo");
fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN);
fooSp.setTitle("FOO SP");
fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL);
fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
try {
mySearchParameterDao.create(fooSp, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertEquals("Resource.xpath is missing", e.getMessage());
}
}
@Test
public void testCreateInvalidParamNoResourceName() {
SearchParameter fooSp = new SearchParameter();
fooSp.setCode("foo");
fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN);
fooSp.setTitle("FOO SP");
fooSp.setXpath("gender");
fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL);
fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
try {
mySearchParameterDao.create(fooSp, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertEquals("Invalid path value \"gender\". Must start with a resource name", e.getMessage());
}
}
@Test
public void testCreateInvalidParamParamNullStatus() {
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);
try {
mySearchParameterDao.create(fooSp, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertEquals("Resource.status is missing or invalid: null", e.getMessage());
}
}
@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<String> 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<String> 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();
}
}

View File

@ -1,7 +1,10 @@
package ca.uhn.fhir.jpa.dao.dstu3; package ca.uhn.fhir.jpa.dao.dstu3;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport; import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport;
@ -12,6 +15,9 @@ import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import ca.uhn.fhir.context.FhirContext; 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.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.ResourceTable;
@ -37,7 +43,29 @@ public class SearchParamExtractorDstu3Test {
Observation obs = new Observation(); Observation obs = new Observation();
obs.addCategory().addCoding().setSystem("SYSTEM").setCode("CODE"); obs.addCategory().addCoding().setSystem("SYSTEM").setCode("CODE");
SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(ourCtx, ourValidationSupport); ISearchParamRegistry searchParamRegistry = new ISearchParamRegistry() {
@Override
public Map<String,RuntimeSearchParam> getActiveSearchParams(String theResourceName) {
RuntimeResourceDefinition nextResDef = ourCtx.getResourceDefinition(theResourceName);
Map<String, RuntimeSearchParam> sps = new HashMap<String, RuntimeSearchParam>();
for (RuntimeSearchParam nextSp : nextResDef.getSearchParams()) {
sps.put(nextSp.getName(), nextSp);
}
return sps;
}
@Override
public void forceRefresh() {
// nothing
}
@Override
public Collection<RuntimeSearchParam> getAllSearchParams(String theResourceName) {
throw new UnsupportedOperationException();
}
};
SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(ourCtx, ourValidationSupport, searchParamRegistry);
Set<BaseResourceIndexedSearchParam> tokens = extractor.extractSearchParamTokens(new ResourceTable(), obs); Set<BaseResourceIndexedSearchParam> tokens = extractor.extractSearchParamTokens(new ResourceTable(), obs);
assertEquals(1, tokens.size()); assertEquals(1, tokens.size());
ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.iterator().next(); ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.iterator().next();

View File

@ -0,0 +1,319 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.CapabilityStatement;
import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestComponent;
import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestResourceComponent;
import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent;
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.SearchParameter;
import org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.rest.gclient.ReferenceClientParam;
import ca.uhn.fhir.rest.gclient.TokenClientParam;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
public class ResourceProviderCustomSearchParamDstu3Test extends BaseResourceProviderDstu3Test {
@Override
@After
public void after() throws Exception {
super.after();
myDaoConfig.setDefaultSearchParamsCanBeOverridden(new DaoConfig().isDefaultSearchParamsCanBeOverridden());
}
@Override
public void before() throws Exception {
super.before();
}
@Test
public void saveCreateSearchParamInvalidWithMissingStatus() throws IOException {
SearchParameter sp = new SearchParameter();
sp.setCode("foo");
sp.setXpath("Patient.gender");
sp.setXpathUsage(XPathUsageType.NORMAL);
sp.setTitle("Foo Param");
try {
ourClient.create().resource(sp).execute();
fail();
} catch (UnprocessableEntityException e) {
assertEquals("HTTP 422 Unprocessable Entity: Resource.status is missing or invalid: null", e.getMessage());
}
}
@Override
@Before
public void beforeResetConfig() {
super.beforeResetConfig();
myDaoConfig.setDefaultSearchParamsCanBeOverridden(new DaoConfig().isDefaultSearchParamsCanBeOverridden());
mySearchParamRegsitry.forceRefresh();
}
@Test
public void testConformanceOverrideAllowed() {
myDaoConfig.setDefaultSearchParamsCanBeOverridden(true);
CapabilityStatement conformance = ourClient
.fetchConformance()
.ofType(CapabilityStatement.class)
.execute();
Map<String, CapabilityStatementRestResourceSearchParamComponent> map = extractSearchParams(conformance, "Patient");
CapabilityStatementRestResourceSearchParamComponent param = map.get("foo");
assertNull(param);
param = map.get("gender");
assertNotNull(param);
// Add a custom search parameter
SearchParameter fooSp = new SearchParameter();
fooSp.setCode("foo");
fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN);
fooSp.setTitle("FOO SP");
fooSp.setXpath("Patient.gender");
fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL);
fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(fooSp, mySrd);
// Disable an existing parameter
fooSp = new SearchParameter();
fooSp.setCode("gender");
fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN);
fooSp.setTitle("Gender");
fooSp.setXpath("Patient.gender");
fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL);
fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.RETIRED);
mySearchParameterDao.create(fooSp, mySrd);
mySearchParamRegsitry.forceRefresh();
conformance = ourClient
.fetchConformance()
.ofType(CapabilityStatement.class)
.execute();
map = extractSearchParams(conformance, "Patient");
param = map.get("foo");
assertEquals("foo", param.getName());
param = map.get("gender");
assertNull(param);
}
@Test
public void testConformanceOverrideNotAllowed() {
myDaoConfig.setDefaultSearchParamsCanBeOverridden(false);
CapabilityStatement conformance = ourClient
.fetchConformance()
.ofType(CapabilityStatement.class)
.execute();
Map<String, CapabilityStatementRestResourceSearchParamComponent> map = extractSearchParams(conformance, "Patient");
CapabilityStatementRestResourceSearchParamComponent param = map.get("foo");
assertNull(param);
param = map.get("gender");
assertNotNull(param);
// Add a custom search parameter
SearchParameter fooSp = new SearchParameter();
fooSp.setCode("foo");
fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN);
fooSp.setTitle("FOO SP");
fooSp.setXpath("Patient.gender");
fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL);
fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(fooSp, mySrd);
// Disable an existing parameter
fooSp = new SearchParameter();
fooSp.setCode("gender");
fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN);
fooSp.setTitle("Gender");
fooSp.setXpath("Patient.gender");
fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL);
fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.RETIRED);
mySearchParameterDao.create(fooSp, mySrd);
mySearchParamRegsitry.forceRefresh();
conformance = ourClient
.fetchConformance()
.ofType(CapabilityStatement.class)
.execute();
map = extractSearchParams(conformance, "Patient");
param = map.get("foo");
assertEquals("foo", param.getName());
param = map.get("gender");
assertNotNull(param);
}
private Map<String, CapabilityStatementRestResourceSearchParamComponent> extractSearchParams(CapabilityStatement conformance, String resType) {
Map<String, CapabilityStatementRestResourceSearchParamComponent> map = new HashMap<String, CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent>();
for (CapabilityStatementRestComponent nextRest : conformance.getRest()) {
for (CapabilityStatementRestResourceComponent nextResource : nextRest.getResource()) {
if (!resType.equals(nextResource.getType())) {
continue;
}
for (CapabilityStatementRestResourceSearchParamComponent nextParam : nextResource.getSearchParam()) {
map.put(nextParam.getName(), nextParam);
}
}
}
return map;
}
@SuppressWarnings("unused")
@Test
public void testSearchWithCustomParam() {
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();
pat2.setGender(AdministrativeGender.FEMALE);
IIdType patId2 = myPatientDao.create(pat2, mySrd).getId().toUnqualifiedVersionless();
SearchParameterMap map;
IBundleProvider results;
List<String> foundResources;
Bundle result;
result = ourClient
.search()
.forResource(Patient.class)
.where(new TokenClientParam("foo").exactly().code("male"))
.returnBundle(Bundle.class)
.execute();
foundResources = toUnqualifiedVersionlessIdValues(result);
assertThat(foundResources, contains(patId.getValue()));
}
@Test
public void testCreatingParamMarksCorrectResourcesForReindexing() {
Patient pat = new Patient();
pat.setGender(AdministrativeGender.MALE);
IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless();
Observation obs2 = new Observation();
obs2.setStatus(ObservationStatus.FINAL);
IIdType obsId = myObservationDao.create(obs2, mySrd).getId().toUnqualifiedVersionless();
ResourceTable res = myResourceTableDao.findOne(patId.getIdPartAsLong());
assertEquals(BaseHapiFhirDao.INDEX_STATUS_INDEXED, res.getIndexStatus().longValue());
res = myResourceTableDao.findOne(obsId.getIdPartAsLong());
assertEquals(BaseHapiFhirDao.INDEX_STATUS_INDEXED, res.getIndexStatus().longValue());
SearchParameter fooSp = new SearchParameter();
fooSp.setCode("foo");
fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN);
fooSp.setTitle("FOO SP");
fooSp.setXpath("Patient.gender");
fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL);
fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(fooSp, mySrd);
res = myResourceTableDao.findOne(patId.getIdPartAsLong());
assertEquals(null, res.getIndexStatus());
res = myResourceTableDao.findOne(obsId.getIdPartAsLong());
assertEquals(BaseHapiFhirDao.INDEX_STATUS_INDEXED, res.getIndexStatus().longValue());
}
@SuppressWarnings("unused")
@Test
public void testSearchQualifiedWithCustomReferenceParam() {
SearchParameter fooSp = new SearchParameter();
fooSp.setCode("foo");
fooSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.REFERENCE);
fooSp.setTitle("FOO SP");
fooSp.setXpath("Observation.subject");
fooSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL);
fooSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(fooSp, mySrd);
mySearchParamRegsitry.forceRefresh();
Patient pat = new Patient();
pat.setGender(AdministrativeGender.MALE);
IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless();
Observation obs1 = new Observation();
obs1.getSubject().setReferenceElement(patId);
IIdType obsId1 = myObservationDao.create(obs1, mySrd).getId().toUnqualifiedVersionless();
Observation obs2 = new Observation();
obs2.setStatus(org.hl7.fhir.dstu3.model.Observation.ObservationStatus.FINAL);
IIdType obsId2 = myObservationDao.create(obs2, mySrd).getId().toUnqualifiedVersionless();
SearchParameterMap map;
IBundleProvider results;
List<String> foundResources;
Bundle result;
result = ourClient
.search()
.forResource(Observation.class)
.where(new ReferenceClientParam("foo").hasChainedProperty(Patient.GENDER.exactly().code("male")))
.returnBundle(Bundle.class)
.execute();
foundResources = toUnqualifiedVersionlessIdValues(result);
assertThat(foundResources, contains(obsId1.getValue()));
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -22,6 +22,7 @@ import org.junit.Test;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeSearchParam; 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.Bundle;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.Conformance; import ca.uhn.fhir.model.dstu.resource.Conformance;
@ -175,8 +176,8 @@ public class DynamicSearchTest {
@Override @Override
public List<RuntimeSearchParam> getSearchParameters() { public List<RuntimeSearchParam> getSearchParameters() {
ArrayList<RuntimeSearchParam> retVal = new ArrayList<RuntimeSearchParam>(); ArrayList<RuntimeSearchParam> retVal = new ArrayList<RuntimeSearchParam>();
retVal.add(new RuntimeSearchParam("param1", "This is the first parameter", "Patient.param1", RestSearchParameterTypeEnum.STRING, 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)); retVal.add(new RuntimeSearchParam("param2", "This is the second parameter", "Patient.param2", RestSearchParameterTypeEnum.DATE, null, null, RuntimeSearchParamStatusEnum.ACTIVE));
return retVal; return retVal;
} }

View File

@ -96,8 +96,11 @@ public class ValidateDstu2_1Test {
params.addParameter().setName("resource").setResource(patient); params.addParameter().setName("resource").setResource(patient);
params.addParameter().setName("mode").setValue(new CodeType(" ")); params.addParameter().setName("mode").setValue(new CodeType(" "));
String encodedResource = ourCtx.newXmlParser().encodeResourceToString(params);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$validate"); HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$validate");
httpPost.setEntity(new StringEntity(ourCtx.newXmlParser().encodeResourceToString(params), ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); httpPost.setEntity(new StringEntity(encodedResource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost); HttpResponse status = ourClient.execute(httpPost);
String resp = IOUtils.toString(status.getEntity().getContent()); String resp = IOUtils.toString(status.getEntity().getContent());

View File

@ -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&param2=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&param2=val2&param3=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&param2=val2&param2:exact=val2e&param3=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<String, List<String>> ourLastAdditionalParams;
public static class DummyPatientResourceProvider implements IResourceProvider {
@Override
public Class<? extends IBaseResource> getResourceType() {
return Patient.class;
}
@Search()
public List<Patient> search01(
@OptionalParam(name = "param1") StringAndListParam theParam1) {
ourLastMethod = "search01";
ourLastParam1 = theParam1;
ArrayList<Patient> retVal = new ArrayList<Patient>();
retVal.add((Patient) new Patient().addName(new HumanName().setFamily("FAMILY")).setId("1"));
return retVal;
}
@Search()
public List<Patient> search02(
@OptionalParam(name = "param1") StringAndListParam theParam1,
@OptionalParam(name = "param2") StringAndListParam theParam2) {
ourLastMethod = "search02";
ourLastParam1 = theParam1;
ourLastParam2 = theParam2;
ArrayList<Patient> retVal = new ArrayList<Patient>();
retVal.add((Patient) new Patient().addName(new HumanName().setFamily("FAMILY")).setId("1"));
return retVal;
}
@Search(allowUnknownParams = true)
public List<Patient> search03(
@OptionalParam(name = "param1") StringAndListParam theParam1,
@OptionalParam(name = "param2") StringAndListParam theParam2,
@RawParam() Map<String, List<String>> theAdditionalParams) {
ourLastMethod = "search03";
ourLastParam1 = theParam1;
ourLastParam2 = theParam2;
ourLastAdditionalParams = theAdditionalParams;
ArrayList<Patient> retVal = new ArrayList<Patient>();
retVal.add((Patient) new Patient().addName(new HumanName().setFamily("FAMILY")).setId("1"));
return retVal;
}
}
}

View File

@ -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

View File

@ -39,7 +39,7 @@ public class ${className}ResourceProvider extends
return ${className}.class; return ${className}.class;
} }
@Search() @Search(allowUnknownParams=true)
public ca.uhn.fhir.rest.server.IBundleProvider search( public ca.uhn.fhir.rest.server.IBundleProvider search(
javax.servlet.http.HttpServletRequest theServletRequest, javax.servlet.http.HttpServletRequest theServletRequest,
@ -99,6 +99,9 @@ public class ${className}ResourceProvider extends
#end #end
#end #end
@RawParam
Map<String, List<String>> theAdditionalRawParams,
#if ( $version != 'dstu' ) #if ( $version != 'dstu' )
@IncludeParam(reverse=true) @IncludeParam(reverse=true)
Set<Include> theRevIncludes, Set<Include> theRevIncludes,
@ -151,6 +154,8 @@ public class ${className}ResourceProvider extends
paramMap.setCount(theCount); paramMap.setCount(theCount);
paramMap.setRequestDetails(theRequestDetails); paramMap.setRequestDetails(theRequestDetails);
getDao().translateRawParameters(theAdditionalRawParams, paramMap);
ca.uhn.fhir.rest.server.IBundleProvider retVal = getDao().search(paramMap); ca.uhn.fhir.rest.server.IBundleProvider retVal = getDao().search(paramMap);
return retVal; return retVal;
} finally { } finally {

View File

@ -19,6 +19,11 @@
</ul> </ul>
]]> ]]>
</action> </action>
<action type="add">
The JPA server now supports custom search parameters in DSTU3
mode. This allows users to create search parameters which contain
custom paths, or even override and disable existing search
parameters.
<action type="fix"> <action type="fix">
CLI example uploader couldn't find STU3 examples after CI server CLI example uploader couldn't find STU3 examples after CI server
was moved to build.fhir.org was moved to build.fhir.org
@ -133,7 +138,7 @@
</ul> </ul>
]]> ]]>
</action> </action>
<action type="fix"> <action type="fix">
Fix issue in AuthorizationIntetceptor where Fix issue in AuthorizationIntetceptor where
transactions are blocked even when they transactions are blocked even when they
should not be should not be
@ -284,6 +289,101 @@
return CapabilityStatement instead of the previously used return CapabilityStatement instead of the previously used
"Conformance" resource "Conformance" resource
</action> </action>
<action type="fix">
CLI example uploader couldn't find STU3 examples after CI server
was moved to build.fhir.org
</action>
<action type="fix">
Fix issue in JPA subscription module that prevented purging stale
subscriptions when many were present on Postgres
</action>
<action type="fix" issue="532">
Server interceptor methods were being called twice unnecessarily
by the JPA server, and the DaoConfig interceptor registration
framework was not actually useful. Thanks to GitHub user
@mattiuusitalo for reporting!
</action>
<action type="fix" issue="503">
AuthorizationInterceptor on JPA server did not correctly
apply rules on deleting resources in a specific compartment
because the resource metadata was stripped by the JPA server
before the interceptor could see it. Thanks to
Eeva Turkka for reporting!
</action>
<action type="fix" issue="519">
JPA server exported CapabilityStatement includes
double entries for the _id parameter and uses the
wrong type (string instead of token). Thanks to
Robert Lichtenberger for reporting!
</action>
<action type="add" issue="504">
Custom resource types which extend Binary must not
have declared extensions since this is invalid in
FHIR (and HAPI would just ignore them anyhow). Thanks
to Thomas S Berg for reporting!
</action>
<action type="add">
Standard HAPI zip/tar distributions did not include the project
sources and JavaDoc JARs. Thanks to Keith Boone for pointing
this out!
</action>
<action type="fix">
Server AuthorizationInterceptor always rejects history operation
at the type level even if rules should allow it.
</action>
<action type="fix">
JPA server terminology service was not correctly validating or expanding codes
in SNOMED CT or LOINC code systems. Thanks to David Hay for reporting!
</action>
<action type="fix" issue="539">
Attempting to search for an invalid resource type (e.g. GET base/FooResource) should
return an HTTP 404 and not a 400, per the HTTP spec. Thanks to
GitHub user @CarthageKing for the pull request!
</action>
<action type="fix" issue="544">
When parsing a Bundle containing placeholder fullUrls and references
(e.g. "urn:uuid:0000-0000") the resource reference targets did not get
populated with the given resources. Note that as a part of this
change, <![CDATA[<code>IdType</code> and <code>IdDt</code>]]> have been modified
so that when parsing a placeholder ID, the complete placeholder including the
"urn:uuid:" or "urn:oid:" prefix will be placed into the ID part. Previously,
the prefix was treated as the base URL, which led to strange behaviour
like the placeholder being treated as a real IDs. Thanks to GitHub
user @jodue for reporting!
</action>
<action type="add">
Declared extensions with multiple type() options listed in the @Child
annotation caused a crash on startup. Now this is supported.
</action>
<action type="add">
STU3 XHTML parser for narrative choked if the narrative contained
an <![CDATA[<code>&amp;rsquot;</code>]]> entity string.
</action>
<action type="fix" issue="538">
When parsing a quantity parameter on the server with a
value and units but no system (e.g.
<![CDATA[<code>GET [base]/Observation?value=5.4||mg</code>]]>)
the unit was incorrectly treated as the system. Thanks to
@CarthageKing for the pull request!
</action>
<action type="533">
Correct a typo in the JPA ValueSet ResourceProvider which prevented
successful operation under Spring 4.3. Thanks to
Robbert van Waveren for the pull request!
</action>
<action type="remove">
Deprecate the method
<![CDATA[<code>ICompositeElement#getAllPopulatedChildElementsOfType(Class)</code>]]>
as it is no longer used by HAPI and is just an annoying step
in creating custom structures. Thanks to Allan Bro Hansen
for pointing this out.
</action>
<action type="fix" issue="547">
CapturingInterceptor did not buffer the response meaning
that in many circumstances it did not actually capture
the response. Thanks to Jenny Syed of Cerner for
the pull request and contribution!
</action>
</release> </release>
<release version="2.1" date="2016-11-11"> <release version="2.1" date="2016-11-11">
<action type="add"> <action type="add">