Fix #19 - Now have a whitelist and a blacklist for parameter names

This commit is contained in:
James Agnew 2014-09-02 16:41:01 -04:00
parent 9217ee28bf
commit c704aa185d
11 changed files with 284 additions and 126 deletions

View File

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

View File

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

View File

@ -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 {};

View File

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

View File

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

View File

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

View File

@ -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"
*/

View File

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

View File

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

View File

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

View File

@ -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")) {