Fix #19 - Now have a whitelist and a blacklist for parameter names
This commit is contained in:
parent
9217ee28bf
commit
c704aa185d
|
@ -70,6 +70,7 @@ import ca.uhn.fhir.rest.param.StringParam;
|
|||
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
|
@ -156,36 +157,82 @@ public List<Patient> findPatients(
|
|||
}
|
||||
//END SNIPPET: underlyingReq
|
||||
|
||||
//START SNIPPET: reference
|
||||
//START SNIPPET: referenceSimple
|
||||
@Search
|
||||
public List<Patient> findPatients(
|
||||
@RequiredParam(name=Patient.SP_PROVIDER) ReferenceParam theProvider
|
||||
public List<Patient> findPatientsWithSimpleReference(
|
||||
@OptionalParam(name=Patient.SP_PROVIDER) ReferenceParam theProvider
|
||||
) {
|
||||
List<Patient> retVal=new ArrayList<Patient>();
|
||||
|
||||
// May be populated with the resource type (e.g. "Patient") if the client requested one
|
||||
String type = theProvider.getResourceType();
|
||||
|
||||
// May be populated with the chain (e.g. "name") if the client requested one
|
||||
String chain = theProvider.getChain();
|
||||
|
||||
/* The actual parameter value. This will be the resource ID if no chain was provided,
|
||||
* but refers to the value of a specific property noted by the chain if one was given.
|
||||
* For example, the following request:
|
||||
* http://example.com/fhir/Patient?provider:Organization.name=FooOrg
|
||||
* has a type of "Organization" and a chain of "name", meaning that
|
||||
* the returned patients should have a provider which is an Organization
|
||||
* with a name matching "FooOrg".
|
||||
*/
|
||||
String value = theProvider.getValue();
|
||||
|
||||
List<Patient> retVal=new ArrayList<Patient>(); // populate this
|
||||
return retVal;
|
||||
// If the parameter passed in includes a resource type (e.g. ?provider:Patient=123)
|
||||
// that resoruce type is available. Here we just check that it is either not provided
|
||||
// or set to "Patient"
|
||||
if (theProvider.hasResourceType()) {
|
||||
String resourceType = theProvider.getResourceType();
|
||||
if ("Patient".equals(resourceType) == false) {
|
||||
throw new InvalidRequestException("Invalid resource type for parameter 'provider': " + resourceType);
|
||||
}
|
||||
}
|
||||
|
||||
if (theProvider != null) {
|
||||
// ReferenceParam extends IdDt so all of the resource ID methods are available
|
||||
String providerId = theProvider.getIdPart();
|
||||
|
||||
// .. populate retVal will Patient resources having provider with id "providerId" ..
|
||||
|
||||
}
|
||||
|
||||
return retVal;
|
||||
|
||||
}
|
||||
//END SNIPPET: reference
|
||||
//END SNIPPET: referenceSimple
|
||||
|
||||
|
||||
//START SNIPPET: referenceChain
|
||||
//START SNIPPET: referenceWithChain
|
||||
@Search
|
||||
public List<DiagnosticReport> findReportsWithChain(
|
||||
@RequiredParam(name=DiagnosticReport.SP_SUBJECT, chainWhitelist= {Patient.SP_FAMILY, Patient.SP_GENDER}) ReferenceParam theSubject
|
||||
) {
|
||||
List<DiagnosticReport> retVal=new ArrayList<DiagnosticReport>();
|
||||
|
||||
String chain = theSubject.getChain();
|
||||
if (Patient.SP_FAMILY.equals(chain)) {
|
||||
String familyName = theSubject.getValue();
|
||||
// .. populate with reports matching subject family name ..
|
||||
}
|
||||
if (Patient.SP_GENDER.equals(chain)) {
|
||||
String gender = theSubject.getValue();
|
||||
// .. populate with reports matching subject gender ..
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
//END SNIPPET: referenceWithChain
|
||||
|
||||
|
||||
//START SNIPPET: referenceWithChainCombo
|
||||
@Search
|
||||
public List<DiagnosticReport> findReportsWithChainCombo (
|
||||
@RequiredParam(name=DiagnosticReport.SP_SUBJECT, chainWhitelist= {"", Patient.SP_FAMILY}) ReferenceParam theSubject
|
||||
) {
|
||||
List<DiagnosticReport> retVal=new ArrayList<DiagnosticReport>();
|
||||
|
||||
String chain = theSubject.getChain();
|
||||
if (Patient.SP_FAMILY.equals(chain)) {
|
||||
String familyName = theSubject.getValue();
|
||||
// .. populate with reports matching subject family name ..
|
||||
}
|
||||
if ("".equals(chain)) {
|
||||
String resourceId = theSubject.getValue();
|
||||
// .. populate with reports matching subject with resource ID ..
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
//END SNIPPET: referenceWithChainCombo
|
||||
|
||||
|
||||
//START SNIPPET: referenceWithStaticChain
|
||||
@Search
|
||||
public List<Patient> findObservations(
|
||||
@RequiredParam(name=Observation.SP_SUBJECT+'.'+Patient.SP_IDENTIFIER) TokenParam theProvider
|
||||
|
@ -200,7 +247,7 @@ public List<Patient> findObservations(
|
|||
return retVal;
|
||||
|
||||
}
|
||||
//END SNIPPET: referenceChain
|
||||
//END SNIPPET: referenceWithStaticChain
|
||||
|
||||
|
||||
//START SNIPPET: read
|
||||
|
|
|
@ -39,6 +39,8 @@ public @interface OptionalParam {
|
|||
|
||||
public static final String ALLOW_CHAIN_ANY = "*";
|
||||
|
||||
public static final String ALLOW_CHAIN_NOTCHAINED = "";
|
||||
|
||||
/**
|
||||
* For reference parameters ({@link ReferenceParam}) this value may be
|
||||
* used to indicate which chain values (if any) are <b>not</b> valid
|
||||
|
@ -54,10 +56,23 @@ public @interface OptionalParam {
|
|||
/**
|
||||
* 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}, as is
|
||||
* the default, all values are valid. Any values specified in
|
||||
* 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:
|
||||
* <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>
|
||||
* <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.
|
||||
|
|
|
@ -35,60 +35,67 @@ import ca.uhn.fhir.rest.param.ReferenceParam;
|
|||
* Parameter annotation which specifies a search parameter for a {@link Search} method.
|
||||
*/
|
||||
public @interface RequiredParam {
|
||||
|
||||
/**
|
||||
* For reference parameters ({@link ReferenceParam}) this value may be used to indicate which chain values (if any)
|
||||
* are <b>not</b> valid for the given parameter. Values here will supercede any values specified in
|
||||
* {@link #chainWhitelist()}
|
||||
* For reference parameters ({@link ReferenceParam}) this value may be used to indicate which chain values (if any) are <b>not</b> valid for the given parameter. Values here will supercede any
|
||||
* values specified in {@link #chainWhitelist()}
|
||||
* <p>
|
||||
* If the parameter annotated with this annotation is not a {@link ReferenceParam}, this value must not be
|
||||
* populated.
|
||||
* If the parameter annotated with this annotation is not a {@link ReferenceParam}, this value must not be populated.
|
||||
* </p>
|
||||
*/
|
||||
String[] chainBlacklist() default {};
|
||||
|
||||
/**
|
||||
* For reference parameters ({@link ReferenceParam}) this value may be used to indicate which chain values (if any)
|
||||
* are valid for the given parameter. If the list contains the value {@link OptionalParam#ALLOW_CHAIN_ANY}, as is
|
||||
* the default, all values are valid. Any values specified in {@link #chainBlacklist()} will supercede (have
|
||||
* priority over) values here.
|
||||
* <p>
|
||||
* If the parameter annotated with this annotation is not a {@link ReferenceParam}, this value must not be
|
||||
* populated.
|
||||
* </p>
|
||||
*/
|
||||
String[] chainWhitelist() default { OptionalParam.ALLOW_CHAIN_ANY };
|
||||
/**
|
||||
* For reference parameters ({@link ReferenceParam}) this value may be
|
||||
* used to indicate which chain values (if any) are valid for the given
|
||||
* parameter. If the list contains the value {@link OptionalParam#ALLOW_CHAIN_ANY}, all values are valid. (this is the default)
|
||||
* If the list contains the value {@link OptionalParam#ALLOW_CHAIN_NOTCHAINED}
|
||||
* then the reference param only supports the empty chain (i.e. the resource
|
||||
* ID).
|
||||
* <p>
|
||||
* Valid values for this parameter include:
|
||||
* <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>
|
||||
* <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 {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.
|
||||
* <p>
|
||||
* If the parameter annotated with this annotation is not a {@link CompositeParam}, this value must not be
|
||||
* populated.
|
||||
* 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 of the URL parameter used to populate this method parameter.
|
||||
* This is the name for the parameter. Generally this should be a simple string (e.g. "name", or "identifier") which will be the name of the URL parameter used to populate this method parameter.
|
||||
* <p>
|
||||
* Most resource model classes have constants which may be used to supply values for this field, e.g.
|
||||
* {@link Patient#SP_NAME} or {@link Observation#SP_DATE}
|
||||
* Most resource model classes have constants which may be used to supply values for this field, e.g. {@link Patient#SP_NAME} or {@link Observation#SP_DATE}
|
||||
* </p>
|
||||
* <p>
|
||||
* If you wish to specify a parameter for a resource reference which only accepts a specific chained value, it is
|
||||
* also valid to supply a chained name here, such as "patient.name". It is recommended to supply this using
|
||||
* constants where possible, e.g. <code>{@link Observation#SP_SUBJECT} + '.' + {@link Patient#SP_IDENTIFIER}</code>
|
||||
* If you wish to specify a parameter for a resource reference which only accepts a specific chained value, it is also valid to supply a chained name here, such as "patient.name". It is
|
||||
* recommended to supply this using constants where possible, e.g. <code>{@link Observation#SP_SUBJECT} + '.' + {@link Patient#SP_IDENTIFIER}</code>
|
||||
* </p>
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* For resource reference parameters ({@link ReferenceParam}) this parameter may be used to indicate the resource
|
||||
* type(s) which may be referenced by this param.
|
||||
* For resource reference parameters ({@link ReferenceParam}) this parameter may be used to indicate the resource type(s) which may be referenced by this param.
|
||||
* <p>
|
||||
* If the parameter annotated with this annotation is not a {@link ReferenceParam}, this value must not be
|
||||
* populated.
|
||||
* If the parameter annotated with this annotation is not a {@link ReferenceParam}, this value must not be populated.
|
||||
* </p>
|
||||
*/
|
||||
Class<? extends IResource>[] targetTypes() default {};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ca.uhn.fhir.rest.method;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.*;
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PushbackReader;
|
||||
|
@ -18,13 +19,10 @@ import javax.servlet.ServletRequest;
|
|||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.swing.text.html.Option;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.client.utils.DateUtils;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
|
|
|
@ -41,6 +41,7 @@ import ca.uhn.fhir.model.api.annotation.Description;
|
|||
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
|
||||
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.annotation.OptionalParam;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
|
||||
import ca.uhn.fhir.rest.param.BaseQueryParameter;
|
||||
|
@ -84,8 +85,8 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
* Check for parameter combinations and names that are invalid
|
||||
*/
|
||||
List<IParameter> parameters = getParameters();
|
||||
List<SearchParameter> searchParameters = new ArrayList<SearchParameter>();
|
||||
for (int i =0; i < parameters.size(); i++) {
|
||||
// List<SearchParameter> searchParameters = new ArrayList<SearchParameter>();
|
||||
for (int i = 0; i < parameters.size(); i++) {
|
||||
IParameter next = parameters.get(i);
|
||||
if (!(next instanceof SearchParameter)) {
|
||||
continue;
|
||||
|
@ -94,20 +95,21 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
SearchParameter sp = (SearchParameter) next;
|
||||
if (sp.getName().startsWith("_")) {
|
||||
if (ALLOWED_PARAMS.contains(sp.getName())) {
|
||||
String msg = getContext().getLocalizer().getMessage(getClass().getName() + ".invalidSpecialParamName", theMethod.getName(), theMethod.getDeclaringClass().getSimpleName(), sp.getName());
|
||||
String msg = getContext().getLocalizer().getMessage(getClass().getName() + ".invalidSpecialParamName", theMethod.getName(), theMethod.getDeclaringClass().getSimpleName(),
|
||||
sp.getName());
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
searchParameters.add(sp);
|
||||
|
||||
// searchParameters.add(sp);
|
||||
}
|
||||
for (int i = 0; i < searchParameters.size(); i++) {
|
||||
SearchParameter next = searchParameters.get(i);
|
||||
// next.
|
||||
}
|
||||
|
||||
// for (int i = 0; i < searchParameters.size(); i++) {
|
||||
// SearchParameter next = searchParameters.get(i);
|
||||
// // next.
|
||||
// }
|
||||
|
||||
/*
|
||||
* Only compartment searching methods may have an ID parameter
|
||||
* Only compartment searching methods may have an ID parameter
|
||||
*/
|
||||
if (isBlank(myCompartmentName) && myIdParamIndex != null) {
|
||||
String msg = theContext.getLocalizer().getMessage(getClass().getName() + ".idWithoutCompartment", theMethod.getName(), theMethod.getDeclaringClass());
|
||||
|
@ -291,14 +293,34 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
ArrayList<String> retVal = new ArrayList<String>(theQualifiedNames.size());
|
||||
for (String next : theQualifiedNames) {
|
||||
String qualifier = "";
|
||||
int idx = next.indexOf('.');
|
||||
if (idx > -1 && next.length() > (idx + 1)) {
|
||||
qualifier = next.substring(idx);
|
||||
|
||||
int start = -1;
|
||||
int end = -1;
|
||||
for (int idx = 0; idx < next.length(); idx++) {
|
||||
char nextChar = next.charAt(idx);
|
||||
if (nextChar == '.' || nextChar == ':') {
|
||||
if (start == -1) {
|
||||
start = idx;
|
||||
} else {
|
||||
end = idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (start != -1) {
|
||||
if (end != -1) {
|
||||
qualifier = next.substring(start, end);
|
||||
} else {
|
||||
qualifier = next.substring(start);
|
||||
}
|
||||
}
|
||||
|
||||
if (theQualifierWhitelist != null) {
|
||||
if (!theQualifierWhitelist.contains(qualifier)) {
|
||||
continue;
|
||||
if (!theQualifierWhitelist.contains(OptionalParam.ALLOW_CHAIN_ANY)) {
|
||||
if (!theQualifierWhitelist.contains(qualifier)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (theQualifierBlacklist != null) {
|
||||
|
@ -311,7 +333,8 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
public static BaseHttpClientInvocation createSearchInvocation(FhirContext theContext, String theResourceName, Map<String, List<String>> theParameters, IdDt theId, String theCompartmentName, SearchStyleEnum theSearchStyle) {
|
||||
public static BaseHttpClientInvocation createSearchInvocation(FhirContext theContext, String theResourceName, Map<String, List<String>> theParameters, IdDt theId, String theCompartmentName,
|
||||
SearchStyleEnum theSearchStyle) {
|
||||
SearchStyleEnum searchStyle = theSearchStyle;
|
||||
if (searchStyle == null) {
|
||||
int length = 0;
|
||||
|
@ -342,8 +365,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
}
|
||||
|
||||
/*
|
||||
* Are we doing a get (GET [base]/Patient?name=foo) or a get with search (GET [base]/Patient/_search?name=foo)
|
||||
* or a post (POST [base]/Patient with parameters in the POST body)
|
||||
* Are we doing a get (GET [base]/Patient?name=foo) or a get with search (GET [base]/Patient/_search?name=foo) or a post (POST [base]/Patient with parameters in the POST body)
|
||||
*/
|
||||
switch (searchStyle) {
|
||||
case GET:
|
||||
|
|
|
@ -68,8 +68,10 @@ import ca.uhn.fhir.rest.param.StringParam;
|
|||
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.util.CollectionUtil;
|
||||
|
||||
/**
|
||||
* Created by dsotnikov on 2/25/2014.
|
||||
|
@ -77,38 +79,49 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|||
@SuppressWarnings("deprecation")
|
||||
public class SearchParameter extends BaseQueryParameter {
|
||||
|
||||
private static final String EMPTY_STRING = "";
|
||||
private static HashMap<Class<?>, SearchParamTypeEnum> ourParamTypes;
|
||||
private static HashMap<SearchParamTypeEnum, Set<String>> ourParamQualifiers;
|
||||
|
||||
static {
|
||||
ourParamTypes = new HashMap<Class<?>, SearchParamTypeEnum>();
|
||||
ourParamQualifiers = new HashMap<SearchParamTypeEnum, Set<String>>();
|
||||
|
||||
ourParamTypes.put(StringParam.class, SearchParamTypeEnum.STRING);
|
||||
ourParamTypes.put(StringOrListParam.class, SearchParamTypeEnum.STRING);
|
||||
ourParamTypes.put(StringAndListParam.class, SearchParamTypeEnum.STRING);
|
||||
ourParamQualifiers.put(SearchParamTypeEnum.STRING, CollectionUtil.newSet(Constants.PARAMQUALIFIER_STRING_EXACT, Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING));
|
||||
|
||||
ourParamTypes.put(TokenParam.class, SearchParamTypeEnum.TOKEN);
|
||||
ourParamTypes.put(TokenOrListParam.class, SearchParamTypeEnum.TOKEN);
|
||||
ourParamTypes.put(TokenAndListParam.class, SearchParamTypeEnum.TOKEN);
|
||||
ourParamQualifiers.put(SearchParamTypeEnum.TOKEN, CollectionUtil.newSet(Constants.PARAMQUALIFIER_TOKEN_TEXT, Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING));
|
||||
|
||||
ourParamTypes.put(DateParam.class, SearchParamTypeEnum.DATE);
|
||||
ourParamTypes.put(DateOrListParam.class, SearchParamTypeEnum.DATE);
|
||||
ourParamTypes.put(DateAndListParam.class, SearchParamTypeEnum.DATE);
|
||||
ourParamTypes.put(DateRangeParam.class, SearchParamTypeEnum.DATE);
|
||||
ourParamQualifiers.put(SearchParamTypeEnum.DATE, CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING));
|
||||
|
||||
ourParamTypes.put(QuantityParam.class, SearchParamTypeEnum.QUANTITY);
|
||||
ourParamTypes.put(QuantityOrListParam.class, SearchParamTypeEnum.QUANTITY);
|
||||
ourParamTypes.put(QuantityAndListParam.class, SearchParamTypeEnum.QUANTITY);
|
||||
ourParamQualifiers.put(SearchParamTypeEnum.QUANTITY, CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING));
|
||||
|
||||
ourParamTypes.put(NumberParam.class, SearchParamTypeEnum.NUMBER);
|
||||
ourParamTypes.put(NumberOrListParam.class, SearchParamTypeEnum.NUMBER);
|
||||
ourParamTypes.put(NumberAndListParam.class, SearchParamTypeEnum.NUMBER);
|
||||
ourParamQualifiers.put(SearchParamTypeEnum.NUMBER, CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING));
|
||||
|
||||
ourParamTypes.put(ReferenceParam.class, SearchParamTypeEnum.REFERENCE);
|
||||
ourParamTypes.put(ReferenceOrListParam.class, SearchParamTypeEnum.REFERENCE);
|
||||
ourParamTypes.put(ReferenceAndListParam.class, SearchParamTypeEnum.REFERENCE);
|
||||
ourParamQualifiers.put(SearchParamTypeEnum.REFERENCE, CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING)); // no empty because that gets added from OptionalParam#chainWhitelist
|
||||
|
||||
ourParamTypes.put(CompositeParam.class, SearchParamTypeEnum.COMPOSITE);
|
||||
ourParamTypes.put(CompositeOrListParam.class, SearchParamTypeEnum.COMPOSITE);
|
||||
ourParamTypes.put(CompositeAndListParam.class, SearchParamTypeEnum.COMPOSITE);
|
||||
ourParamQualifiers.put(SearchParamTypeEnum.COMPOSITE, CollectionUtil.newSet(Constants.PARAMQUALIFIER_MISSING, EMPTY_STRING));
|
||||
}
|
||||
private Set<String> myQualifierBlacklist;
|
||||
private Set<String> myQualifierWhitelist;
|
||||
|
@ -205,12 +218,12 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
|
||||
public void setChainlists(String[] theChainWhitelist, String[] theChainBlacklist) {
|
||||
myQualifierWhitelist = new HashSet<String>(theChainWhitelist.length);
|
||||
|
||||
for (int i = 0; i < theChainWhitelist.length; i++) {
|
||||
if (theChainWhitelist[i].equals(OptionalParam.ALLOW_CHAIN_ANY)) {
|
||||
myQualifierWhitelist = null;
|
||||
break;
|
||||
} else if (theChainWhitelist[i].equals("")) {
|
||||
myQualifierWhitelist.add("");
|
||||
myQualifierWhitelist.add(OptionalParam.ALLOW_CHAIN_ANY);
|
||||
} else if (theChainWhitelist[i].equals(EMPTY_STRING)) {
|
||||
myQualifierWhitelist.add(EMPTY_STRING);
|
||||
} else {
|
||||
myQualifierWhitelist.add('.' + theChainWhitelist[i]);
|
||||
}
|
||||
|
@ -219,8 +232,8 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
if (theChainBlacklist.length > 0) {
|
||||
myQualifierBlacklist = new HashSet<String>(theChainBlacklist.length);
|
||||
for (String next : theChainBlacklist) {
|
||||
if (next.equals("")) {
|
||||
myQualifierBlacklist.add("");
|
||||
if (next.equals(EMPTY_STRING)) {
|
||||
myQualifierBlacklist.add(EMPTY_STRING);
|
||||
} else {
|
||||
myQualifierBlacklist.add('.' + next);
|
||||
}
|
||||
|
@ -264,8 +277,23 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
throw new ConfigurationException("Unsupported data type for parameter: " + type.getCanonicalName());
|
||||
}
|
||||
|
||||
SearchParamTypeEnum typeEnum = ourParamTypes.get(type);
|
||||
if (typeEnum != null) {
|
||||
Set<String> builtInQualifiers = ourParamQualifiers.get(typeEnum);
|
||||
if (builtInQualifiers != null) {
|
||||
if (myQualifierWhitelist != null) {
|
||||
HashSet<String> qualifierWhitelist = new HashSet<String>();
|
||||
qualifierWhitelist.addAll(myQualifierWhitelist);
|
||||
qualifierWhitelist.addAll(builtInQualifiers);
|
||||
myQualifierWhitelist = qualifierWhitelist;
|
||||
} else {
|
||||
myQualifierWhitelist = Collections.unmodifiableSet(builtInQualifiers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (myParamType == null) {
|
||||
myParamType = ourParamTypes.get(type);
|
||||
myParamType = typeEnum;
|
||||
}
|
||||
|
||||
if (myParamType != null) {
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.apache.commons.lang3.StringUtils;
|
|||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum;
|
||||
import ca.uhn.fhir.rest.annotation.OptionalParam;
|
||||
import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
|
||||
import ca.uhn.fhir.rest.method.IParameter;
|
||||
import ca.uhn.fhir.rest.method.QualifiedParamList;
|
||||
|
@ -52,15 +53,15 @@ public abstract class BaseQueryParameter implements IParameter {
|
|||
public abstract boolean isRequired();
|
||||
|
||||
/**
|
||||
* Parameter should return true if {@link #parse(List)} should be called even if the query string contained no
|
||||
* values for the given parameter
|
||||
* Parameter should return true if {@link #parse(List)} should be called even if the query string contained no values for the given parameter
|
||||
*/
|
||||
public abstract boolean handlesMissing();
|
||||
|
||||
public abstract SearchParamTypeEnum getParamType();
|
||||
|
||||
@Override
|
||||
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, BaseHttpClientInvocation theClientInvocation) throws InternalErrorException {
|
||||
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments,
|
||||
BaseHttpClientInvocation theClientInvocation) throws InternalErrorException {
|
||||
if (theSourceClientArgument == null) {
|
||||
if (isRequired()) {
|
||||
throw new NullPointerException("SearchParameter '" + getName() + "' is required and may not be null");
|
||||
|
@ -68,8 +69,8 @@ public abstract class BaseQueryParameter implements IParameter {
|
|||
} else {
|
||||
List<QualifiedParamList> value = encode(theContext, theSourceClientArgument);
|
||||
ArrayList<String> paramValues = new ArrayList<String>(value.size());
|
||||
String qualifier=null;
|
||||
|
||||
String qualifier = null;
|
||||
|
||||
for (QualifiedParamList nextParamEntry : value) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
for (String str : nextParamEntry) {
|
||||
|
@ -79,13 +80,13 @@ public abstract class BaseQueryParameter implements IParameter {
|
|||
b.append(str.replace(",", "\\,"));
|
||||
}
|
||||
paramValues.add(b.toString());
|
||||
|
||||
|
||||
if (StringUtils.isBlank(qualifier)) {
|
||||
qualifier=nextParamEntry.getQualifier();
|
||||
qualifier = nextParamEntry.getQualifier();
|
||||
}
|
||||
}
|
||||
|
||||
theTargetQueryArguments.put(getName()+StringUtils.defaultString(qualifier), paramValues);
|
||||
theTargetQueryArguments.put(getName() + StringUtils.defaultString(qualifier), paramValues);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,8 +118,10 @@ public abstract class BaseQueryParameter implements IParameter {
|
|||
|
||||
private void parseParams(RequestDetails theRequest, List<QualifiedParamList> paramList, String theQualifiedParamName, String theQualifier) {
|
||||
if (getQualifierWhitelist() != null) {
|
||||
if (!getQualifierWhitelist().contains(defaultString(theQualifier)) ){
|
||||
return;
|
||||
if (!getQualifierWhitelist().contains(OptionalParam.ALLOW_CHAIN_ANY)) {
|
||||
if (!getQualifierWhitelist().contains(defaultString(theQualifier))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (getQualifierBlacklist() != null) {
|
||||
|
@ -126,7 +129,7 @@ public abstract class BaseQueryParameter implements IParameter {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String[] value = theRequest.getParameters().get(theQualifiedParamName);
|
||||
if (value != null) {
|
||||
for (String nextParam : value) {
|
||||
|
@ -143,7 +146,7 @@ public abstract class BaseQueryParameter implements IParameter {
|
|||
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
|
||||
// ignore for now
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns null if blacklist is "none"
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package ca.uhn.fhir.util;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class CollectionUtil {
|
||||
|
||||
public static <T> Set<T> newSet(T... theValues) {
|
||||
HashSet<T> retVal = new HashSet<T>();
|
||||
|
||||
for (T t : theValues) {
|
||||
retVal.add(t);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
}
|
|
@ -682,35 +682,54 @@
|
|||
<p>
|
||||
Reference parameters use the
|
||||
<a href="./apidocs/ca/uhn/fhir/rest/param/ReferenceParam.html">ReferenceParam</a>
|
||||
type. Reference param objects being passed into server methods will have three properties
|
||||
populated:
|
||||
type. Reference parameters are, in their most basic form, just a pointer to another
|
||||
resource. For example, you might want to query for DiagnosticReport resources where the
|
||||
subject (the Patient resource that the report is about) is Patient/123. The following
|
||||
example shows a simple resource reference parameter in use.
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="referenceSimple" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
</macro>
|
||||
|
||||
|
||||
<h4>Chained Resource References</h4>
|
||||
|
||||
<p>
|
||||
References may also support a "chained" value. This is a search parameter name
|
||||
on the target resource. For example, you might want to search for DiagnosticReport
|
||||
resources by subject, but use the subject's last name instead of their resource ID.
|
||||
In this example, you are chaining "family" (the last name) to "subject" (the patient).
|
||||
The net result in the query string would look like:<br/>
|
||||
<code>http://fhir.example.com/DiagnosticReport?subject.family=SMITH</code>
|
||||
</p>
|
||||
<p>
|
||||
Chained values must be explicitly declared through the use
|
||||
of a whitelist (or blacklist). The following example shows how to declare a
|
||||
report with an allowable chained parameter:
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="referenceWithChain" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
</macro>
|
||||
|
||||
<p>
|
||||
You may also specify the whitelist value of
|
||||
<code>""</code> to allow an empty chain (e.g. ther resource ID)
|
||||
and this can be combined with other values, as shown below:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<b>The resource type (optional):</b> This is the type of resource
|
||||
being targeted, if the client specifies one in the URL.
|
||||
</li>
|
||||
<li>
|
||||
<b>The chain property (optional):</b> This is the name of the
|
||||
property on the target resource to search for
|
||||
</li>
|
||||
<li>
|
||||
<b>The value (required):</b> This is the actual value to search for. If
|
||||
a chain property has not been specified, this is the ID of the resource.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="reference" />
|
||||
<param name="id" value="referenceWithChainCombo" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
</macro>
|
||||
|
||||
<p>
|
||||
Example URLs to invoke this method:
|
||||
<br />
|
||||
Resource by ID: <code>http://fhir.example.com/Patient?provider=1234</code>
|
||||
Resource by ID: <code>http://fhir.example.com/DiagnosticReport?subject=1234</code>
|
||||
<br />
|
||||
Resource by chained parameter value: <code>http://fhir.example.com/Patient?provider:Organization.name=FooOrg</code>
|
||||
Resource by chained parameter value: <code>http://fhir.example.com/DiagnosticReport?subject.family=SMITH</code>
|
||||
</p>
|
||||
|
||||
<h4>Static Chains</h4>
|
||||
|
@ -724,7 +743,7 @@
|
|||
</p>
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="referenceChain" />
|
||||
<param name="id" value="referenceWithStaticChain" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
</macro>
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -221,7 +221,9 @@ public class ReferenceParameterTest {
|
|||
ServletHandler proxyHandler = new ServletHandler();
|
||||
RestfulServer servlet = new RestfulServer();
|
||||
ourCtx = servlet.getFhirContext();
|
||||
servlet.setResourceProviders(patientProvider, new DummyOrganizationResourceProvider());
|
||||
servlet.setResourceProviders(patientProvider
|
||||
, new DummyOrganizationResourceProvider()
|
||||
);
|
||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||
ourServer.setHandler(proxyHandler);
|
||||
|
|
|
@ -174,7 +174,7 @@ public class StringParameterTest {
|
|||
public static class DummyPatientResourceProvider implements IResourceProvider {
|
||||
|
||||
@Search
|
||||
public List<Patient> findPatient(@RequiredParam(name = "str") StringParam theParam) {
|
||||
public List<Patient> findPatientByStringParam(@RequiredParam(name = "str") StringParam theParam) {
|
||||
ArrayList<Patient> retVal = new ArrayList<Patient>();
|
||||
|
||||
if (theParam.isExact() && theParam.getValue().equals("aaa")) {
|
||||
|
@ -192,7 +192,7 @@ public class StringParameterTest {
|
|||
}
|
||||
|
||||
@Search
|
||||
public List<Patient> findPatient(@RequiredParam(name = "plain") String theParam) {
|
||||
public List<Patient> findPatientByString(@RequiredParam(name = "plain") String theParam) {
|
||||
ArrayList<Patient> retVal = new ArrayList<Patient>();
|
||||
|
||||
if (theParam.toLowerCase().equals("aaa")) {
|
||||
|
|
Loading…
Reference in New Issue