Fix #317 - Allow search params to be used when doing REST operations

This commit is contained in:
James Agnew 2016-03-26 13:44:54 -04:00
parent e5f353c27e
commit b2f11e0bfc
29 changed files with 1833 additions and 286 deletions

View File

@ -2,6 +2,8 @@ package example;
import java.util.List;
import org.hl7.fhir.dstu3.model.Parameters;
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.ConceptMap;
@ -11,10 +13,40 @@ import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenParam;
public class ServerOperations {
//START SNIPPET: searchParamBasic
@Operation(name="$find-matches", idempotent=true)
public Parameters findMatchesBasic(
@OperationParam(name="date") DateParam theDate,
@OperationParam(name="code") TokenParam theCode) {
Parameters retVal = new Parameters();
// Populate bundle with matching resources
return retVal;
}
//END SNIPPET: searchParamBasic
//START SNIPPET: searchParamAdvanced
@Operation(name="$find-matches", idempotent=true)
public Parameters findMatchesAdvanced(
@OperationParam(name="dateRange") DateRangeParam theDate,
@OperationParam(name="name") List<StringParam> theName,
@OperationParam(name="code") TokenAndListParam theEnd) {
Parameters retVal = new Parameters();
// Populate bundle with matching resources
return retVal;
}
//END SNIPPET: searchParamAdvanced
//START SNIPPET: patientTypeOperation
@Operation(name="$everything", idempotent=true)
public Bundle patientTypeOperation(

View File

@ -41,6 +41,24 @@ public @interface OperationParam {
*/
final int MAX_UNLIMITED = -1;
/**
* Value for {@link OperationParam#max()} indicating that the maximum will be inferred
* from the type. If the type is a single parameter type (e.g. <code>StringDt</code>,
* <code>TokenParam</code>, <code>IBaseResource</code>) the maximum will be
* <code>1</code>.
* <p>
* If the type is a collection, e.g.
* <code>List&lt;StringDt&gt;</code> or <code>List&lt;TokenOrListParam&gt;</code>
* the maximum will be set to <code>*</code>. If the param is a search parameter
* "and" type, such as <code>TokenAndListParam</code> the maximum will also be
* set to <code>*</code>
* </p>
*
* @since 1.5
*/
final int MAX_DEFAULT = -2;
/**
* The name of the parameter
*/
@ -66,9 +84,10 @@ public @interface OperationParam {
/**
* The maximum number of repetitions allowed for this child. Should be
* set to {@link #MAX_UNLIMITED} if there is no limit to the number of
* repetitions (default is 1)
* repetitions. See {@link #MAX_DEFAULT} for a description of the default
* behaviour.
*/
int max() default 1;
int max() default MAX_UNLIMITED;
}

View File

@ -1553,6 +1553,33 @@ public class GenericClient extends BaseClient implements IGenericClient {
return this;
}
@Override
public IOperationUntypedWithInputAndPartialOutput andSearchParameter(String theName, IQueryParameterType theValue) {
addParam(theName, theValue);
return this;
}
@SuppressWarnings("unchecked")
@Override
public <T extends IBaseParameters> IOperationUntypedWithInputAndPartialOutput<T> withSearchParameter(Class<T> theParameterType, String theName, IQueryParameterType theValue) {
Validate.notNull(theParameterType, "theParameterType must not be null");
Validate.notEmpty(theName, "theName must not be null");
Validate.notNull(theValue, "theValue must not be null");
myParametersDef = myContext.getResourceDefinition(theParameterType);
myParameters = (IBaseParameters) myParametersDef.newInstance();
addParam(theName, theValue);
return this;
}
private void addParam(String theName, IQueryParameterType theValue) {
IPrimitiveType<?> stringType = ParametersUtil.createString(myContext, theValue.getValueAsQueryToken(myContext));
addParam(theName, stringType);
}
}
private final class OperationOutcomeResponseHandler implements IClientResponseHandler<BaseOperationOutcome> {

View File

@ -24,6 +24,8 @@ import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import ca.uhn.fhir.model.api.IQueryParameterType;
public interface IOperationUntyped {
/**
@ -54,4 +56,16 @@ public interface IOperationUntyped {
*/
<T extends IBaseParameters> IOperationUntypedWithInputAndPartialOutput<T> withParameter(Class<T> theParameterType, String theName, IBase theValue);
/**
* Use chained method calls to construct a Parameters input. This form is a convenience
* in order to allow simple method chaining to be used to build up a parameters
* resource for the input of an operation without needing to manually construct one.
*
* @param theParameterType The type to use for the output parameters (this should be set to
* <code>Parameters.class</code> drawn from the version of the FHIR structures you are using)
* @param theName The first parameter name
* @param theValue The first parameter value
*/
<T extends IBaseParameters> IOperationUntypedWithInputAndPartialOutput<T> withSearchParameter(Class<T> theParameterType, String theName, IQueryParameterType theValue);
}

View File

@ -23,6 +23,8 @@ package ca.uhn.fhir.rest.gclient;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import ca.uhn.fhir.model.api.IQueryParameterType;
public interface IOperationUntypedWithInputAndPartialOutput<T extends IBaseParameters> extends IOperationUntypedWithInput<T> {
/**
@ -35,4 +37,14 @@ public interface IOperationUntypedWithInputAndPartialOutput<T extends IBaseParam
*/
IOperationUntypedWithInputAndPartialOutput<T> andParameter(String theName, IBase theValue);
/**
* Use chained method calls to construct a Parameters input. This form is a convenience
* in order to allow simple method chaining to be used to build up a parameters
* resource for the input of an operation without needing to manually construct one.
*
* @param theName The first parameter name
* @param theValue The first parameter value
*/
IOperationUntypedWithInputAndPartialOutput<T> andSearchParameter(String theName, IQueryParameterType theValue);
}

View File

@ -150,6 +150,9 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
type.setName(next.name());
type.setMin(next.min());
type.setMax(next.max());
if (type.getMax() == OperationParam.MAX_DEFAULT) {
type.setMax(1);
}
if (!next.type().equals(IBase.class)) {
if (next.type().isInterface() || Modifier.isAbstract(next.type().getModifiers())) {
throw new ConfigurationException("Invalid value for @OperationParam.type(): " + next.type().getName());

View File

@ -25,9 +25,11 @@ import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -45,9 +47,13 @@ import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.fhir.model.api.IDatatype;
import ca.uhn.fhir.model.api.IQueryParameterAnd;
import ca.uhn.fhir.model.api.IQueryParameterOr;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.param.BaseAndListParam;
import ca.uhn.fhir.rest.param.CollectionBinder;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -58,8 +64,14 @@ import ca.uhn.fhir.util.ParametersUtil;
public class OperationParameter implements IParameter {
@SuppressWarnings("unchecked")
private static final Class<? extends IQueryParameterType>[] COMPOSITE_TYPES = new Class[0];
static final String REQUEST_CONTENTS_USERDATA_KEY = OperationParam.class.getName() + "_PARSED_RESOURCE";
private boolean myAllowGet;
private final FhirContext myContext;
private IConverter myConverter;
@SuppressWarnings("rawtypes")
private Class<? extends Collection> myInnerCollectionType;
@ -69,8 +81,7 @@ public class OperationParameter implements IParameter {
private final String myOperationName;
private Class<?> myParameterType;
private String myParamType;
private final FhirContext myContext;
private boolean myAllowGet;
private SearchParameter mySearchParameterBinding;
public OperationParameter(FhirContext theCtx, String theOperationName, OperationParam theOperationParam) {
this(theCtx, theOperationName, theOperationParam.name(), theOperationParam.min(), theOperationParam.max());
@ -84,6 +95,21 @@ public class OperationParameter implements IParameter {
myContext = theCtx;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void addValueToList(List<Object> matchingParamValues, Object values) {
if (values != null) {
if (BaseAndListParam.class.isAssignableFrom(myParameterType) && matchingParamValues.size() > 0) {
BaseAndListParam existing = (BaseAndListParam<?>) matchingParamValues.get(0);
BaseAndListParam<?> newAndList = (BaseAndListParam<?>) values;
for (IQueryParameterOr nextAnd : newAndList.getValuesAsQueryTokens()) {
existing.addAnd(nextAnd);
}
} else {
matchingParamValues.add(values);
}
}
}
protected FhirContext getContext() {
return myContext;
}
@ -104,27 +130,60 @@ public class OperationParameter implements IParameter {
return myParamType;
}
public String getSearchParamType() {
if (mySearchParameterBinding != null) {
return mySearchParameterBinding.getParamType().getCode();
} else {
return null;
}
}
@SuppressWarnings("unchecked")
@Override
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
if (getContext().getVersion().getVersion().isRi()) {
if (IDatatype.class.isAssignableFrom(theParameterType)) {
throw new ConfigurationException("Incorrect use of type " + theParameterType.getSimpleName() + " as parameter type for method when context is for version "
+ getContext().getVersion().getVersion().name() + " in method: " + theMethod.toString());
throw new ConfigurationException("Incorrect use of type " + theParameterType.getSimpleName() + " as parameter type for method when context is for version " + getContext().getVersion().getVersion().name() + " in method: " + theMethod.toString());
}
}
myParameterType = theParameterType;
if (theInnerCollectionType != null) {
myInnerCollectionType = CollectionBinder.getInstantiableCollectionType(theInnerCollectionType, myName);
if (myMax == OperationParam.MAX_DEFAULT) {
myMax = OperationParam.MAX_UNLIMITED;
}
} else if (IQueryParameterAnd.class.isAssignableFrom(myParameterType)) {
if (myMax == OperationParam.MAX_DEFAULT) {
myMax = OperationParam.MAX_UNLIMITED;
}
} else {
myMax = 1;
if (myMax == OperationParam.MAX_DEFAULT) {
myMax = 1;
}
}
myAllowGet = IPrimitiveType.class.isAssignableFrom(myParameterType) || String.class.equals(myParameterType);
boolean typeIsConcrete = !myParameterType.isInterface() && !Modifier.isAbstract(myParameterType.getModifiers());
//@formatter:off
boolean isSearchParam =
IQueryParameterType.class.isAssignableFrom(myParameterType) ||
IQueryParameterOr.class.isAssignableFrom(myParameterType) ||
IQueryParameterAnd.class.isAssignableFrom(myParameterType);
//@formatter:off
/*
* The parameter can be of type string for validation methods - This is a bit weird. See ValidateDstu2Test. We should probably clean this up..
* Note: We say here !IBase.class.isAssignableFrom because a bunch of DSTU1/2 datatypes also
* extend this interface. I'm not sure if they should in the end.. but they do, so we
* exclude them.
*/
isSearchParam &= typeIsConcrete && !IBase.class.isAssignableFrom(myParameterType);
myAllowGet = IPrimitiveType.class.isAssignableFrom(myParameterType) || String.class.equals(myParameterType) || isSearchParam;
/*
* The parameter can be of type string for validation methods - This is a bit weird. See ValidateDstu2Test. We
* should probably clean this up..
*/
if (!myParameterType.equals(IBase.class) && !myParameterType.equals(String.class)) {
if (IBaseResource.class.isAssignableFrom(myParameterType) && myParameterType.isInterface()) {
@ -135,13 +194,20 @@ public class OperationParameter implements IParameter {
myAllowGet = true;
} else if (myParameterType.equals(ValidationModeEnum.class)) {
// this is ok
} else if (!IBase.class.isAssignableFrom(myParameterType) || myParameterType.isInterface() || Modifier.isAbstract(myParameterType.getModifiers())) {
throw new ConfigurationException("Invalid type for @OperationParam: " + myParameterType.getName());
} else if (myParameterType.equals(ValidationModeEnum.class)) {
myParamType = "code";
} else {
} else if (IBase.class.isAssignableFrom(myParameterType) && typeIsConcrete) {
myParamType = myContext.getElementDefinition((Class<? extends IBase>) myParameterType).getName();
} else if (isSearchParam) {
myParamType = "string";
mySearchParameterBinding = new SearchParameter(myName, myMin > 0);
mySearchParameterBinding.setCompositeTypes(COMPOSITE_TYPES);
mySearchParameterBinding.setType(theParameterType, theInnerCollectionType, theOuterCollectionType);
myConverter = new QueryParameterConverter();
} else {
throw new ConfigurationException("Invalid type for @OperationParam: " + myParameterType.getName());
}
}
}
@ -151,9 +217,12 @@ public class OperationParameter implements IParameter {
return this;
}
private void throwWrongParamType(Object nextValue) {
throw new InvalidRequestException("Request has parameter " + myName + " of type " + nextValue.getClass().getSimpleName() + " but method expects type " + myParameterType.getSimpleName());
}
@Override
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource)
throws InternalErrorException {
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException {
assert theTargetResource != null;
Object sourceClientArgument = theSourceClientArgument;
if (sourceClientArgument == null) {
@ -173,46 +242,77 @@ public class OperationParameter implements IParameter {
List<Object> matchingParamValues = new ArrayList<Object>();
if (theRequest.getRequestType() == RequestTypeEnum.GET) {
String[] paramValues = theRequest.getParameters().get(myName);
if (paramValues != null && paramValues.length > 0) {
if (myAllowGet) {
if (mySearchParameterBinding != null) {
if (DateRangeParam.class.isAssignableFrom(myParameterType)) {
List<QualifiedParamList> parameters = new ArrayList<QualifiedParamList>();
parameters.add(QualifiedParamList.singleton(paramValues[0]));
if (paramValues.length > 1) {
parameters.add(QualifiedParamList.singleton(paramValues[1]));
}
DateRangeParam dateRangeParam = new DateRangeParam();
dateRangeParam.setValuesAsQueryTokens(parameters);
matchingParamValues.add(dateRangeParam);
} else if (String.class.isAssignableFrom(myParameterType)) {
for (String next : paramValues) {
matchingParamValues.add(next);
}
List<QualifiedParamList> params = new ArrayList<QualifiedParamList>();
String nameWithQualifierColon = myName + ":";
for (String nextParamName : theRequest.getParameters().keySet()) {
String qualifier;
if (nextParamName.equals(myName)) {
qualifier = null;
} else if (nextParamName.startsWith(nameWithQualifierColon)) {
qualifier = nextParamName.substring(nextParamName.indexOf(':'));
} else {
for (String nextValue : paramValues) {
FhirContext ctx = theRequest.getServer().getFhirContext();
RuntimePrimitiveDatatypeDefinition def = (RuntimePrimitiveDatatypeDefinition) ctx.getElementDefinition((Class<? extends IBase>) myParameterType);
IPrimitiveType<?> instance = def.newInstance();
instance.setValueAsString(nextValue);
matchingParamValues.add(instance);
// This is some other parameter, not the one bound by this instance
continue;
}
String[] values = theRequest.getParameters().get(nextParamName);
if (values != null) {
for (String nextValue : values) {
params.add(QualifiedParamList.splitQueryStringByCommasIgnoreEscape(qualifier, nextValue));
}
}
} else {
HapiLocalizer localizer = theRequest.getServer().getFhirContext().getLocalizer();
String msg = localizer.getMessage(OperationParameter.class, "urlParamNotPrimitive", myOperationName, myName);
throw new MethodNotAllowedException(msg, RequestTypeEnum.POST);
}
if (!params.isEmpty()) {
for (QualifiedParamList next : params) {
Object values = mySearchParameterBinding.parse(myContext, Collections.singletonList(next));
addValueToList(matchingParamValues, values);
}
}
} else {
String[] paramValues = theRequest.getParameters().get(myName);
if (paramValues != null && paramValues.length > 0) {
if (myAllowGet) {
if (DateRangeParam.class.isAssignableFrom(myParameterType)) {
List<QualifiedParamList> parameters = new ArrayList<QualifiedParamList>();
parameters.add(QualifiedParamList.singleton(paramValues[0]));
if (paramValues.length > 1) {
parameters.add(QualifiedParamList.singleton(paramValues[1]));
}
DateRangeParam dateRangeParam = new DateRangeParam();
dateRangeParam.setValuesAsQueryTokens(parameters);
matchingParamValues.add(dateRangeParam);
} else if (String.class.isAssignableFrom(myParameterType)) {
for (String next : paramValues) {
matchingParamValues.add(next);
}
} else {
for (String nextValue : paramValues) {
FhirContext ctx = theRequest.getServer().getFhirContext();
RuntimePrimitiveDatatypeDefinition def = (RuntimePrimitiveDatatypeDefinition) ctx.getElementDefinition((Class<? extends IBase>) myParameterType);
IPrimitiveType<?> instance = def.newInstance();
instance.setValueAsString(nextValue);
matchingParamValues.add(instance);
}
}
} else {
HapiLocalizer localizer = theRequest.getServer().getFhirContext().getLocalizer();
String msg = localizer.getMessage(OperationParameter.class, "urlParamNotPrimitive", myOperationName, myName);
throw new MethodNotAllowedException(msg, RequestTypeEnum.POST);
}
}
}
} else {
FhirContext ctx = theRequest.getServer().getFhirContext();
IBaseResource requestContents = (IBaseResource) theRequest.getUserData().get(REQUEST_CONTENTS_USERDATA_KEY);
RuntimeResourceDefinition def = ctx.getResourceDefinition(requestContents);
RuntimeResourceDefinition def = myContext.getResourceDefinition(requestContents);
if (def.getName().equals("Parameters")) {
BaseRuntimeChildDefinition paramChild = def.getChildByName("parameter");
@ -247,7 +347,7 @@ public class OperationParameter implements IParameter {
}
} else {
if (myParameterType.isAssignableFrom(requestContents.getClass())) {
tryToAddValues(Arrays.asList((IBase) requestContents), matchingParamValues);
}
@ -264,8 +364,7 @@ public class OperationParameter implements IParameter {
}
try {
@SuppressWarnings("rawtypes")
Collection retVal = myInnerCollectionType.newInstance();
Collection<Object> retVal = myInnerCollectionType.newInstance();
retVal.addAll(matchingParamValues);
return retVal;
} catch (InstantiationException e) {
@ -301,14 +400,11 @@ public class OperationParameter implements IParameter {
}
throwWrongParamType(nextValue);
}
theMatchingParamValues.add(nextValue);
addValueToList(theMatchingParamValues, nextValue);
}
}
private void throwWrongParamType(Object nextValue) {
throw new InvalidRequestException("Request has parameter " + myName + " of type " + nextValue.getClass().getSimpleName() + " but method expects type " + myParameterType.getSimpleName());
}
public interface IConverter {
Object incomingServer(Object theObject);
@ -317,4 +413,27 @@ public class OperationParameter implements IParameter {
}
private class QueryParameterConverter implements IConverter {
public QueryParameterConverter() {
Validate.isTrue(mySearchParameterBinding != null);
}
@Override
public Object incomingServer(Object theObject) {
IPrimitiveType<?> obj = (IPrimitiveType<?>) theObject;
List<QualifiedParamList> paramList = Collections.singletonList(QualifiedParamList.splitQueryStringByCommasIgnoreEscape(null, obj.getValueAsString()));
return mySearchParameterBinding.parse(myContext, paramList);
}
@Override
public Object outgoingClient(Object theObject) {
IQueryParameterType obj = (IQueryParameterType) theObject;
IPrimitiveType<?> retVal = (IPrimitiveType<?>) myContext.getElementDefinition("string").newInstance();
retVal.setValueAsString(obj.getValueAsQueryToken(myContext));
return retVal;
}
}
}

View File

@ -27,9 +27,9 @@ import ca.uhn.fhir.model.api.IQueryParameterOr;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.method.QualifiedParamList;
abstract class BaseOrListParam<T extends IQueryParameterType> implements IQueryParameterOr<T> {
abstract class BaseOrListParam<MT extends BaseOrListParam<?, ?>, PT extends IQueryParameterType> implements IQueryParameterOr<PT> {
private List<T> myList=new ArrayList<T>();
private List<PT> myList=new ArrayList<PT>();
// public void addToken(T theParam) {
// Validate.notNull(theParam,"Param can not be null");
@ -40,25 +40,26 @@ abstract class BaseOrListParam<T extends IQueryParameterType> implements IQueryP
public void setValuesAsQueryTokens(QualifiedParamList theParameters) {
myList.clear();
for (String next : theParameters) {
T nextParam = newInstance();
PT nextParam = newInstance();
nextParam.setValueAsQueryToken(theParameters.getQualifier(), next);
myList.add(nextParam);
}
}
abstract T newInstance();
abstract PT newInstance();
public abstract BaseOrListParam<T> addOr(T theParameter);
public abstract MT addOr(PT theParameter);
public BaseOrListParam<T> add(T theParameter) {
@SuppressWarnings("unchecked")
public MT add(PT theParameter) {
if (theParameter != null) {
myList.add(theParameter);
}
return this;
return (MT) this;
}
@Override
public List<T> getValuesAsQueryTokens() {
public List<PT> getValuesAsQueryTokens() {
return myList;
}

View File

@ -24,7 +24,7 @@ import ca.uhn.fhir.util.CoverageIgnore;
*/
public class CompositeOrListParam<A extends IQueryParameterType, B extends IQueryParameterType> extends BaseOrListParam<CompositeParam<A,B>> {
public class CompositeOrListParam<A extends IQueryParameterType, B extends IQueryParameterType> extends BaseOrListParam<CompositeOrListParam<?,?>, CompositeParam<A,B>> {
private Class<A> myLeftType;
private Class<B> myRightType;

View File

@ -23,7 +23,7 @@ import ca.uhn.fhir.util.CoverageIgnore;
*/
public class DateOrListParam extends BaseOrListParam<DateParam> {
public class DateOrListParam extends BaseOrListParam<DateOrListParam, DateParam> {
@Override
DateParam newInstance() {

View File

@ -23,7 +23,7 @@ import ca.uhn.fhir.util.CoverageIgnore;
*/
public class NumberOrListParam extends BaseOrListParam<NumberParam> {
public class NumberOrListParam extends BaseOrListParam<NumberOrListParam, NumberParam> {
@Override
NumberParam newInstance() {

View File

@ -23,7 +23,7 @@ import ca.uhn.fhir.util.CoverageIgnore;
*/
public class QuantityOrListParam extends BaseOrListParam<QuantityParam> {
public class QuantityOrListParam extends BaseOrListParam<QuantityOrListParam, QuantityParam> {
@Override
QuantityParam newInstance() {

View File

@ -23,7 +23,7 @@ import ca.uhn.fhir.util.CoverageIgnore;
*/
public class ReferenceOrListParam extends BaseOrListParam<ReferenceParam> {
public class ReferenceOrListParam extends BaseOrListParam<ReferenceOrListParam, ReferenceParam> {
@CoverageIgnore
@Override

View File

@ -23,7 +23,7 @@ import ca.uhn.fhir.util.CoverageIgnore;
*/
public class StringOrListParam extends BaseOrListParam<StringParam> {
public class StringOrListParam extends BaseOrListParam<StringOrListParam, StringParam> {
@CoverageIgnore
@Override

View File

@ -31,7 +31,7 @@ import ca.uhn.fhir.util.CoverageIgnore;
* This class represents a restful search operation parameter for an "OR list" of tokens (in other words, a
* list which can contain one-or-more tokens, where the server should return results matching any of the tokens)
*/
public class TokenOrListParam extends BaseOrListParam<TokenParam> {
public class TokenOrListParam extends BaseOrListParam<TokenOrListParam, TokenParam> {
/**
* Create a new empty token "OR list"

View File

@ -112,20 +112,24 @@ public class TokenParam extends BaseParam implements IQueryParameterType {
if (theQualifier != null) {
TokenParamModifier modifier = TokenParamModifier.forValue(theQualifier);
setModifier(modifier);
setSystem(null);
setValue(ParameterUtil.unescape(theParameter));
if (modifier == TokenParamModifier.TEXT) {
setSystem(null);
setValue(ParameterUtil.unescape(theParameter));
return;
}
}
setSystem(null);
if (theParameter == null) {
setValue(null);
} else {
setSystem(null);
if (theParameter == null) {
setValue(null);
int barIndex = ParameterUtil.nonEscapedIndexOf(theParameter, '|');
if (barIndex != -1) {
setSystem(theParameter.substring(0, barIndex));
setValue(ParameterUtil.unescape(theParameter.substring(barIndex + 1)));
} else {
int barIndex = ParameterUtil.nonEscapedIndexOf(theParameter, '|');
if (barIndex != -1) {
setSystem(theParameter.substring(0, barIndex));
setValue(ParameterUtil.unescape(theParameter.substring(barIndex + 1)));
} else {
setValue(ParameterUtil.unescape(theParameter));
}
setValue(ParameterUtil.unescape(theParameter));
}
}
}

View File

@ -23,7 +23,7 @@ import ca.uhn.fhir.util.CoverageIgnore;
*/
public class UriOrListParam extends BaseOrListParam<UriParam> {
public class UriOrListParam extends BaseOrListParam<UriOrListParam, UriParam> {
@CoverageIgnore
@Override

View File

@ -68,15 +68,21 @@ public class ParametersUtil {
IBase parameter = paramChildElem.newInstance();
paramChild.getMutator().addValue(theTargetResource, parameter);
IPrimitiveType<?> value;
if (theContext.getVersion().getVersion().isRi()) {
value = (IPrimitiveType<?>) theContext.getElementDefinition("string").newInstance(theName);
} else {
value = new StringDt(theName);
}
value = createString(theContext, theName);
paramChildElem.getChildByName("name").getMutator().addValue(parameter, value);
return parameter;
}
public static IPrimitiveType<?> createString(FhirContext theContext, String theValue) {
IPrimitiveType<?> value;
if (theContext.getVersion().getVersion().isRi()) {
value = (IPrimitiveType<?>) theContext.getElementDefinition("string").newInstance(theValue);
} else {
value = new StringDt(theValue);
}
return value;
}
public static IBaseParameters newInstance(FhirContext theContext) {
return (IBaseParameters) theContext.getResourceDefinition("Parameters").newInstance();
}

View File

@ -29,8 +29,39 @@ import java.lang.reflect.WildcardType;
import java.util.LinkedHashSet;
import java.util.List;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.model.api.IQueryParameterType;
public class ReflectionUtil {
public static LinkedHashSet<Method> getDeclaredMethods(Class<?> theClazz) {
LinkedHashSet<Method> retVal = new LinkedHashSet<Method>();
for (Method next : theClazz.getDeclaredMethods()) {
try {
Method method = theClazz.getMethod(next.getName(), next.getParameterTypes());
retVal.add(method);
} catch (NoSuchMethodException e) {
retVal.add(next);
} catch (SecurityException e) {
retVal.add(next);
}
}
return retVal;
}
public static Class<?> getGenericCollectionTypeOfField(Field next) {
Class<?> type;
ParameterizedType collectionType = (ParameterizedType) next.getGenericType();
Type firstArg = collectionType.getActualTypeArguments()[0];
if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) {
ParameterizedType pt = ((ParameterizedType) firstArg);
type = (Class<?>) pt.getRawType();
} else {
type = (Class<?>) firstArg;
}
return type;
}
/**
* For a field of type List<Enumeration<Foo>>, returns Foo
*/
@ -52,19 +83,6 @@ public class ReflectionUtil {
return type;
}
public static Class<?> getGenericCollectionTypeOfField(Field next) {
Class<?> type;
ParameterizedType collectionType = (ParameterizedType) next.getGenericType();
Type firstArg = collectionType.getActualTypeArguments()[0];
if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) {
ParameterizedType pt = ((ParameterizedType) firstArg);
type = (Class<?>) pt.getRawType();
} else {
type = (Class<?>) firstArg;
}
return type;
}
public static Class<?> getGenericCollectionTypeOfMethodParameter(Method theMethod, int theParamIndex) {
Class<?> type;
Type genericParameterType = theMethod.getGenericParameterTypes()[theParamIndex];
@ -106,19 +124,16 @@ public class ReflectionUtil {
return type;
}
public static LinkedHashSet<Method> getDeclaredMethods(Class<?> theClazz) {
LinkedHashSet<Method> retVal = new LinkedHashSet<Method>();
for (Method next : theClazz.getDeclaredMethods()) {
try {
Method method = theClazz.getMethod(next.getName(), next.getParameterTypes());
retVal.add(method);
} catch (NoSuchMethodException e) {
retVal.add(next);
} catch (SecurityException e) {
retVal.add(next);
}
/**
* Instantiate a class by no-arg constructor, throw {@link ConfigurationException} if we fail to do so
*/
@CoverageIgnore
public static <T> T newInstance(Class<T> theType) {
try {
return theType.newInstance();
} catch (Exception e) {
throw new ConfigurationException("Failed to instantiate " + theType.getName(), e);
}
return retVal;
}
}

View File

@ -77,7 +77,6 @@ public class DefaultThymeleafNarrativeGeneratorTestDstu2 {
enc.addIdentifier().setSystem("urn:visits").setValue("1234567");
enc.setClassElement(EncounterClassEnum.AMBULATORY);
enc.setPeriod(new PeriodDt().setStart(new DateTimeDt("2001-01-02T11:11:00")));
enc.setType(ca.uhn.fhir.model.dstu2.valueset.EncounterTypeEnum.ANNUAL_DIABETES_MELLITUS_SCREENING);
NarrativeDt narrative = new NarrativeDt();
myGen.generateNarrative(ourCtx, enc, narrative);

View File

@ -49,13 +49,13 @@ import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.util.PortUtil;
public class OperationServerTest {
public class OperationServerDstu2Test {
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx;
private static StringDt ourLastParam1;
private static Patient ourLastParam2;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OperationServerTest.class);
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OperationServerDstu2Test.class);
private static int ourPort;
private static IdDt ourLastId;
private static Server ourServer;

View File

@ -0,0 +1,416 @@
package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
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.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.model.dstu2.resource.Conformance;
import ca.uhn.fhir.model.dstu2.resource.OperationDefinition;
import ca.uhn.fhir.model.dstu2.resource.Parameters;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.StringOrListParam;
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.param.TokenParamModifier;
import ca.uhn.fhir.rest.server.provider.dstu2.ServerConformanceProvider;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.UrlUtil;
public class OperationServerWithSearchParamTypesDstu2Test {
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx;
private static String ourLastMethod;
private static List<StringOrListParam> ourLastParamValStr;
private static List<TokenOrListParam> ourLastParamValTok;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OperationServerWithSearchParamTypesDstu2Test.class);
private static int ourPort;
private static Server ourServer;
@Before
public void before() {
ourLastMethod = "";
ourLastParamValStr = null;
ourLastParamValTok = null;
}
private HttpServletRequest createHttpServletRequest() {
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getRequestURI()).thenReturn("/FhirStorm/fhir/Patient/_search");
when(req.getServletPath()).thenReturn("/fhir");
when(req.getRequestURL()).thenReturn(new StringBuffer().append("http://fhirstorm.dyndns.org:8080/FhirStorm/fhir/Patient/_search"));
when(req.getContextPath()).thenReturn("/FhirStorm");
return req;
}
private ServletConfig createServletConfig() {
ServletConfig sc = mock(ServletConfig.class);
when(sc.getServletContext()).thenReturn(null);
return sc;
}
@Test
public void testAndListWithParameters() throws Exception {
Parameters p = new Parameters();
p.addParameter().setName("valstr").setValue(new StringDt("VALSTR1A,VALSTR1B"));
p.addParameter().setName("valstr").setValue(new StringDt("VALSTR2A,VALSTR2B"));
p.addParameter().setName("valtok").setValue(new StringDt("VALTOK1A|VALTOK1B"));
p.addParameter().setName("valtok").setValue(new StringDt("VALTOK2A|VALTOK2B"));
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$andlist");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
ourLog.info(response);
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(2, ourLastParamValStr.size());
assertEquals(2, ourLastParamValStr.get(0).getValuesAsQueryTokens().size());
assertEquals("VALSTR1A", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALSTR1B", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(1).getValue());
assertEquals("VALSTR2A", ourLastParamValStr.get(1).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALSTR2B", ourLastParamValStr.get(1).getValuesAsQueryTokens().get(1).getValue());
assertEquals(2, ourLastParamValTok.size());
assertEquals(1, ourLastParamValTok.get(0).getValuesAsQueryTokens().size());
assertEquals("VALTOK1A", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOK1B", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALTOK2A", ourLastParamValTok.get(1).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOK2B", ourLastParamValTok.get(1).getValuesAsQueryTokens().get(0).getValue());
assertEquals("type $orlist", ourLastMethod);
}
@Test
public void testAndListWithUrl() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$andlist?valstr=VALSTR1A,VALSTR1B&valstr=VALSTR2A,VALSTR2B&valtok=" + UrlUtil.escape("VALTOK1A|VALTOK1B") + "&valtok="
+ UrlUtil.escape("VALTOK2A|VALTOK2B"));
HttpResponse status = ourClient.execute(httpGet);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
ourLog.info(response);
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(2, ourLastParamValStr.size());
assertEquals(2, ourLastParamValStr.get(0).getValuesAsQueryTokens().size());
assertEquals("VALSTR1A", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALSTR1B", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(1).getValue());
assertEquals("VALSTR2A", ourLastParamValStr.get(1).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALSTR2B", ourLastParamValStr.get(1).getValuesAsQueryTokens().get(1).getValue());
assertEquals(2, ourLastParamValTok.size());
assertEquals(1, ourLastParamValTok.get(0).getValuesAsQueryTokens().size());
assertEquals("VALTOK1A", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOK1B", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALTOK2A", ourLastParamValTok.get(1).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOK2B", ourLastParamValTok.get(1).getValuesAsQueryTokens().get(0).getValue());
assertEquals("type $orlist", ourLastMethod);
}
@Test
public void testGenerateConformance() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
rs.setProviders(new PatientProvider());
ServerConformanceProvider sc = new ServerConformanceProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
Conformance conformance = sc.getServerConformance(createHttpServletRequest());
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
//@formatter:off
assertThat(conf, stringContainsInOrder(
"<type value=\"Patient\"/>",
"<operation>",
"<name value=\"$andlist\"/>"
));
assertThat(conf, stringContainsInOrder(
"<type value=\"Patient\"/>",
"<operation>",
"<name value=\"$nonrepeating\"/>"
));
assertThat(conf, stringContainsInOrder(
"<type value=\"Patient\"/>",
"<operation>",
"<name value=\"$orlist\"/>"
));
//@formatter:on
/*
* Check the operation definitions themselves
*/
OperationDefinition andListDef = sc.readOperationDefinition(new IdDt("OperationDefinition/andlist"));
String def = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(andListDef);
ourLog.info(def);
//@formatter:off
assertThat(def, stringContainsInOrder(
"<parameter>",
"<name value=\"valtok\"/>",
"<use value=\"in\"/>",
"<min value=\"0\"/>",
"<max value=\"10\"/>",
"<type value=\"string\"/>",
"</parameter>"
));
//@formatter:on
}
@Test
public void testNonRepeatingWithParams() throws Exception {
Parameters p = new Parameters();
p.addParameter().setName("valstr").setValue(new StringDt("VALSTR"));
p.addParameter().setName("valtok").setValue(new StringDt("VALTOKA|VALTOKB"));
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$nonrepeating");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
ourLog.info(response);
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(1, ourLastParamValStr.size());
assertEquals(1, ourLastParamValStr.get(0).getValuesAsQueryTokens().size());
assertEquals("VALSTR", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals(1, ourLastParamValTok.size());
assertEquals(1, ourLastParamValTok.get(0).getValuesAsQueryTokens().size());
assertEquals("VALTOKA", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOKB", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("type $nonrepeating", ourLastMethod);
}
@Test
public void testNonRepeatingWithUrl() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$nonrepeating?valstr=VALSTR&valtok=" + UrlUtil.escape("VALTOKA|VALTOKB"));
HttpResponse status = ourClient.execute(httpGet);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
ourLog.info(response);
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(1, ourLastParamValStr.size());
assertEquals(1, ourLastParamValStr.get(0).getValuesAsQueryTokens().size());
assertEquals("VALSTR", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals(1, ourLastParamValTok.size());
assertEquals(1, ourLastParamValTok.get(0).getValuesAsQueryTokens().size());
assertEquals("VALTOKA", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOKB", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("type $nonrepeating", ourLastMethod);
}
@Test
public void testNonRepeatingWithUrlQualified() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$nonrepeating?valstr:exact=VALSTR&valtok:not=" + UrlUtil.escape("VALTOKA|VALTOKB"));
HttpResponse status = ourClient.execute(httpGet);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
ourLog.info(response);
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(1, ourLastParamValStr.size());
assertEquals(1, ourLastParamValStr.get(0).getValuesAsQueryTokens().size());
assertEquals("VALSTR", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(0).getValue());
assertTrue(ourLastParamValStr.get(0).getValuesAsQueryTokens().get(0).isExact());
assertEquals(1, ourLastParamValTok.size());
assertEquals(1, ourLastParamValTok.get(0).getValuesAsQueryTokens().size());
assertEquals("VALTOKA", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOKB", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals(TokenParamModifier.NOT, ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getModifier());
assertEquals("type $nonrepeating", ourLastMethod);
}
@Test
public void testOrListWithParameters() throws Exception {
Parameters p = new Parameters();
p.addParameter().setName("valstr").setValue(new StringDt("VALSTR1A,VALSTR1B"));
p.addParameter().setName("valstr").setValue(new StringDt("VALSTR2A,VALSTR2B"));
p.addParameter().setName("valtok").setValue(new StringDt("VALTOK1A|VALTOK1B"));
p.addParameter().setName("valtok").setValue(new StringDt("VALTOK2A|VALTOK2B"));
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$orlist");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
ourLog.info(response);
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(2, ourLastParamValStr.size());
assertEquals(2, ourLastParamValStr.get(0).getValuesAsQueryTokens().size());
assertEquals("VALSTR1A", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALSTR1B", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(1).getValue());
assertEquals("VALSTR2A", ourLastParamValStr.get(1).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALSTR2B", ourLastParamValStr.get(1).getValuesAsQueryTokens().get(1).getValue());
assertEquals(2, ourLastParamValTok.size());
assertEquals(1, ourLastParamValTok.get(0).getValuesAsQueryTokens().size());
assertEquals("VALTOK1A", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOK1B", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALTOK2A", ourLastParamValTok.get(1).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOK2B", ourLastParamValTok.get(1).getValuesAsQueryTokens().get(0).getValue());
assertEquals("type $orlist", ourLastMethod);
}
@Test
public void testOrListWithUrl() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$orlist?valstr=VALSTR1A,VALSTR1B&valstr=VALSTR2A,VALSTR2B&valtok=" + UrlUtil.escape("VALTOK1A|VALTOK1B") + "&valtok="
+ UrlUtil.escape("VALTOK2A|VALTOK2B"));
HttpResponse status = ourClient.execute(httpGet);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
ourLog.info(response);
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(2, ourLastParamValStr.size());
assertEquals(2, ourLastParamValStr.get(0).getValuesAsQueryTokens().size());
assertEquals("VALSTR1A", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALSTR1B", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(1).getValue());
assertEquals("VALSTR2A", ourLastParamValStr.get(1).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALSTR2B", ourLastParamValStr.get(1).getValuesAsQueryTokens().get(1).getValue());
assertEquals(2, ourLastParamValTok.size());
assertEquals(1, ourLastParamValTok.get(0).getValuesAsQueryTokens().size());
assertEquals("VALTOK1A", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOK1B", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALTOK2A", ourLastParamValTok.get(1).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOK2B", ourLastParamValTok.get(1).getValuesAsQueryTokens().get(0).getValue());
assertEquals("type $orlist", ourLastMethod);
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourCtx = FhirContext.forDstu2();
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
servlet.setPagingProvider(new FifoMemoryPagingProvider(10).setDefaultPageSize(2));
servlet.setFhirContext(ourCtx);
servlet.setResourceProviders(new 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();
}
public static class PatientProvider implements IResourceProvider {
@Operation(name = "$andlist", idempotent = true)
public Parameters andlist(
//@formatter:off
@OperationParam(name="valstr", max=10) StringAndListParam theValStr,
@OperationParam(name="valtok", max=10) TokenAndListParam theValTok
//@formatter:on
) {
ourLastMethod = "type $orlist";
ourLastParamValStr = theValStr.getValuesAsQueryTokens();
ourLastParamValTok = theValTok.getValuesAsQueryTokens();
return createEmptyParams();
}
/**
* Just so we have something to return
*/
private Parameters createEmptyParams() {
Parameters retVal = new Parameters();
retVal.setId("100");
return retVal;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return Patient.class;
}
@Operation(name = "$nonrepeating", idempotent = true)
public Parameters nonrepeating(
//@formatter:off
@OperationParam(name="valstr") StringParam theValStr,
@OperationParam(name="valtok") TokenParam theValTok
//@formatter:on
) {
ourLastMethod = "type $nonrepeating";
ourLastParamValStr = Collections.singletonList(new StringOrListParam().add(theValStr));
ourLastParamValTok = Collections.singletonList(new TokenOrListParam().add(theValTok));
return createEmptyParams();
}
@Operation(name = "$orlist", idempotent = true)
public Parameters orlist(
//@formatter:off
@OperationParam(name="valstr", max=10) List<StringOrListParam> theValStr,
@OperationParam(name="valtok", max=10) List<TokenOrListParam> theValTok
//@formatter:on
) {
ourLastMethod = "type $orlist";
ourLastParamValStr = theValStr;
ourLastParamValTok = theValTok;
return createEmptyParams();
}
}
}

View File

@ -45,7 +45,7 @@ public class ReadDstu2Test {
@Before
public void before() {
ourServlet.setAddProfileTag(AddProfileTagEnum.NEVER);
ourCtx.setAddProfileTagWhenEncoding(AddProfileTagEnum.NEVER);
ourInitializeProfileList = false;
ourLastId = null;
}
@ -55,7 +55,7 @@ public class ReadDstu2Test {
*/
@Test
public void testAddProfile() throws Exception {
ourServlet.setAddProfileTag(AddProfileTagEnum.ALWAYS);
ourCtx.setAddProfileTagWhenEncoding(AddProfileTagEnum.ALWAYS);
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123?_format=xml");
HttpResponse status = ourClient.execute(httpGet);
@ -74,7 +74,7 @@ public class ReadDstu2Test {
@Test
public void testVread() throws Exception {
ourServlet.setAddProfileTag(AddProfileTagEnum.ALWAYS);
ourCtx.setAddProfileTagWhenEncoding(AddProfileTagEnum.ALWAYS);
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/_history/1");
HttpResponse status = ourClient.execute(httpGet);
@ -99,18 +99,19 @@ public class ReadDstu2Test {
@Test
public void testAddProfileToExistingList() throws Exception {
ourInitializeProfileList = true;
ourCtx.setAddProfileTagWhenEncoding(AddProfileTagEnum.ONLY_FOR_CUSTOM);
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123&_format=xml");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, containsString("p1ReadValue"));
assertThat(responseContent, containsString("p1ReadId"));
assertEquals("<Patient xmlns=\"http://hl7.org/fhir\"><id value=\"p1ReadId\"/><meta><profile value=\"http://foo\"/><profile value=\"http://foo_profile\"/></meta><identifier><value value=\"p1ReadValue\"/></identifier></Patient>", responseContent);
ourLog.info(responseContent);
}
/**
@ -118,7 +119,7 @@ public class ReadDstu2Test {
*/
@Test
public void testReadJson() throws Exception {
ourServlet.setAddProfileTag(AddProfileTagEnum.ALWAYS);
ourCtx.setAddProfileTagWhenEncoding(AddProfileTagEnum.ALWAYS);
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123?_format=json");
HttpResponse status = ourClient.execute(httpGet);

View File

@ -538,6 +538,9 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
if (nextParam.getParamType() != null) {
param.setType(nextParam.getParamType());
}
if (nextParam.getSearchParamType() != null) {
param.getSearchTypeElement().setValueAsString(nextParam.getSearchParamType());
}
param.setMin(nextParam.getMin());
param.setMax(nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax()));
param.setName(nextParam.getName());

View File

@ -29,21 +29,19 @@ package org.hl7.fhir.dstu3.model;
*/
// Generated on Sat, Jan 30, 2016 09:18-0500 for FHIR v1.3.0
// Generated on Sat, Mar 26, 2016 11:49-0400 for FHIR v1.3.0
import java.util.*;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.dstu3.model.Enumerations.*;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.model.api.annotation.SearchParamDefinition;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.api.annotation.Block;
import org.hl7.fhir.dstu3.exceptions.FHIRException;
import org.hl7.fhir.dstu3.model.Enumerations.*;
import org.hl7.fhir.instance.model.api.*;
import org.hl7.fhir.dstu3.exceptions.FHIRException;
/**
* A formal computable definition of an operation (on the RESTful interface) or a named query (using the search interaction).
*/
@ -230,7 +228,7 @@ public class OperationDefinition extends DomainResource {
* The name of an individual to contact regarding the operation definition.
*/
@Child(name = "name", type = {StringType.class}, order=1, min=0, max=1, modifier=false, summary=true)
@Description(shortDefinition="Name of a individual to contact", formalDefinition="The name of an individual to contact regarding the operation definition." )
@Description(shortDefinition="Name of an individual to contact", formalDefinition="The name of an individual to contact regarding the operation definition." )
protected StringType name;
/**
@ -399,8 +397,7 @@ public class OperationDefinition extends DomainResource {
}
public boolean isEmpty() {
return super.isEmpty() && (name == null || name.isEmpty()) && (telecom == null || telecom.isEmpty())
;
return super.isEmpty() && ca.uhn.fhir.util.ElementUtil.isEmpty( name, telecom);
}
public String fhirType() {
@ -454,10 +451,17 @@ public class OperationDefinition extends DomainResource {
@Description(shortDefinition="What type this parameter has", formalDefinition="The type for this parameter." )
protected CodeType type;
/**
* How the parameter is understood as a search parameter. This is only used if the parameter type is 'string'.
*/
@Child(name = "searchType", type = {CodeType.class}, order=7, min=0, max=1, modifier=false, summary=false)
@Description(shortDefinition="number | date | string | token | reference | composite | quantity | uri", formalDefinition="How the parameter is understood as a search parameter. This is only used if the parameter type is 'string'." )
protected Enumeration<SearchParamType> searchType;
/**
* A profile the specifies the rules that this parameter must conform to.
*/
@Child(name = "profile", type = {StructureDefinition.class}, order=7, min=0, max=1, modifier=false, summary=false)
@Child(name = "profile", type = {StructureDefinition.class}, order=8, min=0, max=1, modifier=false, summary=false)
@Description(shortDefinition="Profile on the type", formalDefinition="A profile the specifies the rules that this parameter must conform to." )
protected Reference profile;
@ -469,18 +473,18 @@ public class OperationDefinition extends DomainResource {
/**
* Binds to a value set if this parameter is coded (code, Coding, CodeableConcept).
*/
@Child(name = "binding", type = {}, order=8, min=0, max=1, modifier=false, summary=false)
@Child(name = "binding", type = {}, order=9, min=0, max=1, modifier=false, summary=false)
@Description(shortDefinition="ValueSet details if this is coded", formalDefinition="Binds to a value set if this parameter is coded (code, Coding, CodeableConcept)." )
protected OperationDefinitionParameterBindingComponent binding;
/**
* The parts of a Tuple Parameter.
*/
@Child(name = "part", type = {OperationDefinitionParameterComponent.class}, order=9, min=0, max=Child.MAX_UNLIMITED, modifier=false, summary=false)
@Child(name = "part", type = {OperationDefinitionParameterComponent.class}, order=10, min=0, max=Child.MAX_UNLIMITED, modifier=false, summary=false)
@Description(shortDefinition="Parts of a Tuple Parameter", formalDefinition="The parts of a Tuple Parameter." )
protected List<OperationDefinitionParameterComponent> part;
private static final long serialVersionUID = -1514145741L;
private static final long serialVersionUID = -885506257L;
/**
* Constructor
@ -778,6 +782,55 @@ public class OperationDefinition extends DomainResource {
return this;
}
/**
* @return {@link #searchType} (How the parameter is understood as a search parameter. This is only used if the parameter type is 'string'.). This is the underlying object with id, value and extensions. The accessor "getSearchType" gives direct access to the value
*/
public Enumeration<SearchParamType> getSearchTypeElement() {
if (this.searchType == null)
if (Configuration.errorOnAutoCreate())
throw new Error("Attempt to auto-create OperationDefinitionParameterComponent.searchType");
else if (Configuration.doAutoCreate())
this.searchType = new Enumeration<SearchParamType>(new SearchParamTypeEnumFactory()); // bb
return this.searchType;
}
public boolean hasSearchTypeElement() {
return this.searchType != null && !this.searchType.isEmpty();
}
public boolean hasSearchType() {
return this.searchType != null && !this.searchType.isEmpty();
}
/**
* @param value {@link #searchType} (How the parameter is understood as a search parameter. This is only used if the parameter type is 'string'.). This is the underlying object with id, value and extensions. The accessor "getSearchType" gives direct access to the value
*/
public OperationDefinitionParameterComponent setSearchTypeElement(Enumeration<SearchParamType> value) {
this.searchType = value;
return this;
}
/**
* @return How the parameter is understood as a search parameter. This is only used if the parameter type is 'string'.
*/
public SearchParamType getSearchType() {
return this.searchType == null ? null : this.searchType.getValue();
}
/**
* @param value How the parameter is understood as a search parameter. This is only used if the parameter type is 'string'.
*/
public OperationDefinitionParameterComponent setSearchType(SearchParamType value) {
if (value == null)
this.searchType = null;
else {
if (this.searchType == null)
this.searchType = new Enumeration<SearchParamType>(new SearchParamTypeEnumFactory());
this.searchType.setValue(value);
}
return this;
}
/**
* @return {@link #profile} (A profile the specifies the rules that this parameter must conform to.)
*/
@ -894,6 +947,7 @@ public class OperationDefinition extends DomainResource {
childrenList.add(new Property("max", "string", "The maximum number of times this element is permitted to appear in the request or response.", 0, java.lang.Integer.MAX_VALUE, max));
childrenList.add(new Property("documentation", "string", "Describes the meaning or use of this parameter.", 0, java.lang.Integer.MAX_VALUE, documentation));
childrenList.add(new Property("type", "code", "The type for this parameter.", 0, java.lang.Integer.MAX_VALUE, type));
childrenList.add(new Property("searchType", "code", "How the parameter is understood as a search parameter. This is only used if the parameter type is 'string'.", 0, java.lang.Integer.MAX_VALUE, searchType));
childrenList.add(new Property("profile", "Reference(StructureDefinition)", "A profile the specifies the rules that this parameter must conform to.", 0, java.lang.Integer.MAX_VALUE, profile));
childrenList.add(new Property("binding", "", "Binds to a value set if this parameter is coded (code, Coding, CodeableConcept).", 0, java.lang.Integer.MAX_VALUE, binding));
childrenList.add(new Property("part", "@OperationDefinition.parameter", "The parts of a Tuple Parameter.", 0, java.lang.Integer.MAX_VALUE, part));
@ -913,6 +967,8 @@ public class OperationDefinition extends DomainResource {
this.documentation = castToString(value); // StringType
else if (name.equals("type"))
this.type = castToCode(value); // CodeType
else if (name.equals("searchType"))
this.searchType = new SearchParamTypeEnumFactory().fromType(value); // Enumeration<SearchParamType>
else if (name.equals("profile"))
this.profile = castToReference(value); // Reference
else if (name.equals("binding"))
@ -943,6 +999,9 @@ public class OperationDefinition extends DomainResource {
else if (name.equals("type")) {
throw new FHIRException("Cannot call addChild on a primitive type OperationDefinition.type");
}
else if (name.equals("searchType")) {
throw new FHIRException("Cannot call addChild on a primitive type OperationDefinition.searchType");
}
else if (name.equals("profile")) {
this.profile = new Reference();
return this.profile;
@ -967,6 +1026,7 @@ public class OperationDefinition extends DomainResource {
dst.max = max == null ? null : max.copy();
dst.documentation = documentation == null ? null : documentation.copy();
dst.type = type == null ? null : type.copy();
dst.searchType = searchType == null ? null : searchType.copy();
dst.profile = profile == null ? null : profile.copy();
dst.binding = binding == null ? null : binding.copy();
if (part != null) {
@ -986,8 +1046,8 @@ public class OperationDefinition extends DomainResource {
OperationDefinitionParameterComponent o = (OperationDefinitionParameterComponent) other;
return compareDeep(name, o.name, true) && compareDeep(use, o.use, true) && compareDeep(min, o.min, true)
&& compareDeep(max, o.max, true) && compareDeep(documentation, o.documentation, true) && compareDeep(type, o.type, true)
&& compareDeep(profile, o.profile, true) && compareDeep(binding, o.binding, true) && compareDeep(part, o.part, true)
;
&& compareDeep(searchType, o.searchType, true) && compareDeep(profile, o.profile, true) && compareDeep(binding, o.binding, true)
&& compareDeep(part, o.part, true);
}
@Override
@ -999,14 +1059,12 @@ public class OperationDefinition extends DomainResource {
OperationDefinitionParameterComponent o = (OperationDefinitionParameterComponent) other;
return compareValues(name, o.name, true) && compareValues(use, o.use, true) && compareValues(min, o.min, true)
&& compareValues(max, o.max, true) && compareValues(documentation, o.documentation, true) && compareValues(type, o.type, true)
;
&& compareValues(searchType, o.searchType, true);
}
public boolean isEmpty() {
return super.isEmpty() && (name == null || name.isEmpty()) && (use == null || use.isEmpty())
&& (min == null || min.isEmpty()) && (max == null || max.isEmpty()) && (documentation == null || documentation.isEmpty())
&& (type == null || type.isEmpty()) && (profile == null || profile.isEmpty()) && (binding == null || binding.isEmpty())
&& (part == null || part.isEmpty());
return super.isEmpty() && ca.uhn.fhir.util.ElementUtil.isEmpty( name, use, min, max, documentation
, type, searchType, profile, binding, part);
}
public String fhirType() {
@ -1202,8 +1260,7 @@ public class OperationDefinition extends DomainResource {
}
public boolean isEmpty() {
return super.isEmpty() && (strength == null || strength.isEmpty()) && (valueSet == null || valueSet.isEmpty())
;
return super.isEmpty() && ca.uhn.fhir.util.ElementUtil.isEmpty( strength, valueSet);
}
public String fhirType() {
@ -1255,27 +1312,27 @@ public class OperationDefinition extends DomainResource {
@Description(shortDefinition="If for testing purposes, not real usage", formalDefinition="This profile was authored for testing purposes (or education/evaluation/marketing), and is not intended to be used for genuine usage." )
protected BooleanType experimental;
/**
* The date this version of the operation definition was published. The date must change when the business version changes, if it does, and it must change if the status code changes. In addition, it should change when the substantive content of the Operation Definition changes.
*/
@Child(name = "date", type = {DateTimeType.class}, order=6, min=0, max=1, modifier=false, summary=false)
@Description(shortDefinition="Date for this version of the operation definition", formalDefinition="The date this version of the operation definition was published. The date must change when the business version changes, if it does, and it must change if the status code changes. In addition, it should change when the substantive content of the Operation Definition changes." )
protected DateTimeType date;
/**
* The name of the individual or organization that published the operation definition.
*/
@Child(name = "publisher", type = {StringType.class}, order=6, min=0, max=1, modifier=false, summary=true)
@Child(name = "publisher", type = {StringType.class}, order=7, min=0, max=1, modifier=false, summary=true)
@Description(shortDefinition="Name of the publisher (Organization or individual)", formalDefinition="The name of the individual or organization that published the operation definition." )
protected StringType publisher;
/**
* Contacts to assist a user in finding and communicating with the publisher.
*/
@Child(name = "contact", type = {}, order=7, min=0, max=Child.MAX_UNLIMITED, modifier=false, summary=true)
@Child(name = "contact", type = {}, order=8, min=0, max=Child.MAX_UNLIMITED, modifier=false, summary=true)
@Description(shortDefinition="Contact details of the publisher", formalDefinition="Contacts to assist a user in finding and communicating with the publisher." )
protected List<OperationDefinitionContactComponent> contact;
/**
* The date this version of the operation definition was published. The date must change when the business version changes, if it does, and it must change if the status code changes. In addition, it should change when the substantive content of the Operation Definition changes.
*/
@Child(name = "date", type = {DateTimeType.class}, order=8, min=0, max=1, modifier=false, summary=false)
@Description(shortDefinition="Date for this version of the operation definition", formalDefinition="The date this version of the operation definition was published. The date must change when the business version changes, if it does, and it must change if the status code changes. In addition, it should change when the substantive content of the Operation Definition changes." )
protected DateTimeType date;
/**
* A free text natural language description of the profile and its use.
*/
@ -1283,38 +1340,45 @@ public class OperationDefinition extends DomainResource {
@Description(shortDefinition="Natural language description of the operation", formalDefinition="A free text natural language description of the profile and its use." )
protected StringType description;
/**
* The content was developed with a focus and intent of supporting the contexts that are listed. These terms may be used to assist with indexing and searching of operation definitions.
*/
@Child(name = "useContext", type = {CodeableConcept.class}, order=10, min=0, max=Child.MAX_UNLIMITED, modifier=false, summary=true)
@Description(shortDefinition="Content intends to support these contexts", formalDefinition="The content was developed with a focus and intent of supporting the contexts that are listed. These terms may be used to assist with indexing and searching of operation definitions." )
protected List<CodeableConcept> useContext;
/**
* Explains why this operation definition is needed and why it's been constrained as it has.
*/
@Child(name = "requirements", type = {StringType.class}, order=10, min=0, max=1, modifier=false, summary=false)
@Description(shortDefinition="Why is this needed?", formalDefinition="Explains why this operation definition is needed and why it's been constrained as it has." )
@Child(name = "requirements", type = {StringType.class}, order=11, min=0, max=1, modifier=false, summary=false)
@Description(shortDefinition="Why this resource has been created", formalDefinition="Explains why this operation definition is needed and why it's been constrained as it has." )
protected StringType requirements;
/**
* Operations that are idempotent (see [HTTP specification definition of idempotent](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html)) may be invoked by performing an HTTP GET operation instead of a POST.
*/
@Child(name = "idempotent", type = {BooleanType.class}, order=11, min=0, max=1, modifier=false, summary=false)
@Description(shortDefinition="Whether content is unchanged by operation", formalDefinition="Operations that are idempotent (see [HTTP specification definition of idempotent](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html)) may be invoked by performing an HTTP GET operation instead of a POST." )
@Child(name = "idempotent", type = {BooleanType.class}, order=12, min=0, max=1, modifier=false, summary=false)
@Description(shortDefinition="Whether content is unchanged by the operation", formalDefinition="Operations that are idempotent (see [HTTP specification definition of idempotent](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html)) may be invoked by performing an HTTP GET operation instead of a POST." )
protected BooleanType idempotent;
/**
* The name used to invoke the operation.
*/
@Child(name = "code", type = {CodeType.class}, order=12, min=1, max=1, modifier=false, summary=false)
@Child(name = "code", type = {CodeType.class}, order=13, min=1, max=1, modifier=false, summary=false)
@Description(shortDefinition="Name used to invoke the operation", formalDefinition="The name used to invoke the operation." )
protected CodeType code;
/**
* Additional information about how to use this operation or named query.
*/
@Child(name = "notes", type = {StringType.class}, order=13, min=0, max=1, modifier=false, summary=false)
@Child(name = "comment", type = {StringType.class}, order=14, min=0, max=1, modifier=false, summary=false)
@Description(shortDefinition="Additional information about use", formalDefinition="Additional information about how to use this operation or named query." )
protected StringType notes;
protected StringType comment;
/**
* Indicates that this operation definition is a constraining profile on the base.
*/
@Child(name = "base", type = {OperationDefinition.class}, order=14, min=0, max=1, modifier=false, summary=false)
@Child(name = "base", type = {OperationDefinition.class}, order=15, min=0, max=1, modifier=false, summary=false)
@Description(shortDefinition="Marks this as a profile of the base", formalDefinition="Indicates that this operation definition is a constraining profile on the base." )
protected Reference base;
@ -1326,32 +1390,32 @@ public class OperationDefinition extends DomainResource {
/**
* Indicates whether this operation or named query can be invoked at the system level (e.g. without needing to choose a resource type for the context).
*/
@Child(name = "system", type = {BooleanType.class}, order=15, min=1, max=1, modifier=false, summary=false)
@Child(name = "system", type = {BooleanType.class}, order=16, min=1, max=1, modifier=false, summary=false)
@Description(shortDefinition="Invoke at the system level?", formalDefinition="Indicates whether this operation or named query can be invoked at the system level (e.g. without needing to choose a resource type for the context)." )
protected BooleanType system;
/**
* Indicates whether this operation or named query can be invoked at the resource type level for any given resource type level (e.g. without needing to choose a resource type for the context).
*/
@Child(name = "type", type = {CodeType.class}, order=16, min=0, max=Child.MAX_UNLIMITED, modifier=false, summary=false)
@Child(name = "type", type = {CodeType.class}, order=17, min=0, max=Child.MAX_UNLIMITED, modifier=false, summary=false)
@Description(shortDefinition="Invoke at resource level for these type", formalDefinition="Indicates whether this operation or named query can be invoked at the resource type level for any given resource type level (e.g. without needing to choose a resource type for the context)." )
protected List<CodeType> type;
/**
* Indicates whether this operation can be invoked on a particular instance of one of the given types.
*/
@Child(name = "instance", type = {BooleanType.class}, order=17, min=1, max=1, modifier=false, summary=false)
@Child(name = "instance", type = {BooleanType.class}, order=18, min=1, max=1, modifier=false, summary=false)
@Description(shortDefinition="Invoke on an instance?", formalDefinition="Indicates whether this operation can be invoked on a particular instance of one of the given types." )
protected BooleanType instance;
/**
* The parameters for the operation/query.
*/
@Child(name = "parameter", type = {}, order=18, min=0, max=Child.MAX_UNLIMITED, modifier=false, summary=false)
@Child(name = "parameter", type = {}, order=19, min=0, max=Child.MAX_UNLIMITED, modifier=false, summary=false)
@Description(shortDefinition="Parameters for the operation/query", formalDefinition="The parameters for the operation/query." )
protected List<OperationDefinitionParameterComponent> parameter;
private static final long serialVersionUID = 148203484L;
private static final long serialVersionUID = 1780846105L;
/**
* Constructor
@ -1651,6 +1715,55 @@ public class OperationDefinition extends DomainResource {
return this;
}
/**
* @return {@link #date} (The date this version of the operation definition was published. The date must change when the business version changes, if it does, and it must change if the status code changes. In addition, it should change when the substantive content of the Operation Definition changes.). This is the underlying object with id, value and extensions. The accessor "getDate" gives direct access to the value
*/
public DateTimeType getDateElement() {
if (this.date == null)
if (Configuration.errorOnAutoCreate())
throw new Error("Attempt to auto-create OperationDefinition.date");
else if (Configuration.doAutoCreate())
this.date = new DateTimeType(); // bb
return this.date;
}
public boolean hasDateElement() {
return this.date != null && !this.date.isEmpty();
}
public boolean hasDate() {
return this.date != null && !this.date.isEmpty();
}
/**
* @param value {@link #date} (The date this version of the operation definition was published. The date must change when the business version changes, if it does, and it must change if the status code changes. In addition, it should change when the substantive content of the Operation Definition changes.). This is the underlying object with id, value and extensions. The accessor "getDate" gives direct access to the value
*/
public OperationDefinition setDateElement(DateTimeType value) {
this.date = value;
return this;
}
/**
* @return The date this version of the operation definition was published. The date must change when the business version changes, if it does, and it must change if the status code changes. In addition, it should change when the substantive content of the Operation Definition changes.
*/
public Date getDate() {
return this.date == null ? null : this.date.getValue();
}
/**
* @param value The date this version of the operation definition was published. The date must change when the business version changes, if it does, and it must change if the status code changes. In addition, it should change when the substantive content of the Operation Definition changes.
*/
public OperationDefinition setDate(Date value) {
if (value == null)
this.date = null;
else {
if (this.date == null)
this.date = new DateTimeType();
this.date.setValue(value);
}
return this;
}
/**
* @return {@link #publisher} (The name of the individual or organization that published the operation definition.). This is the underlying object with id, value and extensions. The accessor "getPublisher" gives direct access to the value
*/
@ -1740,55 +1853,6 @@ public class OperationDefinition extends DomainResource {
return this;
}
/**
* @return {@link #date} (The date this version of the operation definition was published. The date must change when the business version changes, if it does, and it must change if the status code changes. In addition, it should change when the substantive content of the Operation Definition changes.). This is the underlying object with id, value and extensions. The accessor "getDate" gives direct access to the value
*/
public DateTimeType getDateElement() {
if (this.date == null)
if (Configuration.errorOnAutoCreate())
throw new Error("Attempt to auto-create OperationDefinition.date");
else if (Configuration.doAutoCreate())
this.date = new DateTimeType(); // bb
return this.date;
}
public boolean hasDateElement() {
return this.date != null && !this.date.isEmpty();
}
public boolean hasDate() {
return this.date != null && !this.date.isEmpty();
}
/**
* @param value {@link #date} (The date this version of the operation definition was published. The date must change when the business version changes, if it does, and it must change if the status code changes. In addition, it should change when the substantive content of the Operation Definition changes.). This is the underlying object with id, value and extensions. The accessor "getDate" gives direct access to the value
*/
public OperationDefinition setDateElement(DateTimeType value) {
this.date = value;
return this;
}
/**
* @return The date this version of the operation definition was published. The date must change when the business version changes, if it does, and it must change if the status code changes. In addition, it should change when the substantive content of the Operation Definition changes.
*/
public Date getDate() {
return this.date == null ? null : this.date.getValue();
}
/**
* @param value The date this version of the operation definition was published. The date must change when the business version changes, if it does, and it must change if the status code changes. In addition, it should change when the substantive content of the Operation Definition changes.
*/
public OperationDefinition setDate(Date value) {
if (value == null)
this.date = null;
else {
if (this.date == null)
this.date = new DateTimeType();
this.date.setValue(value);
}
return this;
}
/**
* @return {@link #description} (A free text natural language description of the profile and its use.). This is the underlying object with id, value and extensions. The accessor "getDescription" gives direct access to the value
*/
@ -1838,6 +1902,46 @@ public class OperationDefinition extends DomainResource {
return this;
}
/**
* @return {@link #useContext} (The content was developed with a focus and intent of supporting the contexts that are listed. These terms may be used to assist with indexing and searching of operation definitions.)
*/
public List<CodeableConcept> getUseContext() {
if (this.useContext == null)
this.useContext = new ArrayList<CodeableConcept>();
return this.useContext;
}
public boolean hasUseContext() {
if (this.useContext == null)
return false;
for (CodeableConcept item : this.useContext)
if (!item.isEmpty())
return true;
return false;
}
/**
* @return {@link #useContext} (The content was developed with a focus and intent of supporting the contexts that are listed. These terms may be used to assist with indexing and searching of operation definitions.)
*/
// syntactic sugar
public CodeableConcept addUseContext() { //3
CodeableConcept t = new CodeableConcept();
if (this.useContext == null)
this.useContext = new ArrayList<CodeableConcept>();
this.useContext.add(t);
return t;
}
// syntactic sugar
public OperationDefinition addUseContext(CodeableConcept t) { //3
if (t == null)
return this;
if (this.useContext == null)
this.useContext = new ArrayList<CodeableConcept>();
this.useContext.add(t);
return this;
}
/**
* @return {@link #requirements} (Explains why this operation definition is needed and why it's been constrained as it has.). This is the underlying object with id, value and extensions. The accessor "getRequirements" gives direct access to the value
*/
@ -1978,50 +2082,50 @@ public class OperationDefinition extends DomainResource {
}
/**
* @return {@link #notes} (Additional information about how to use this operation or named query.). This is the underlying object with id, value and extensions. The accessor "getNotes" gives direct access to the value
* @return {@link #comment} (Additional information about how to use this operation or named query.). This is the underlying object with id, value and extensions. The accessor "getComment" gives direct access to the value
*/
public StringType getNotesElement() {
if (this.notes == null)
public StringType getCommentElement() {
if (this.comment == null)
if (Configuration.errorOnAutoCreate())
throw new Error("Attempt to auto-create OperationDefinition.notes");
throw new Error("Attempt to auto-create OperationDefinition.comment");
else if (Configuration.doAutoCreate())
this.notes = new StringType(); // bb
return this.notes;
this.comment = new StringType(); // bb
return this.comment;
}
public boolean hasNotesElement() {
return this.notes != null && !this.notes.isEmpty();
public boolean hasCommentElement() {
return this.comment != null && !this.comment.isEmpty();
}
public boolean hasNotes() {
return this.notes != null && !this.notes.isEmpty();
public boolean hasComment() {
return this.comment != null && !this.comment.isEmpty();
}
/**
* @param value {@link #notes} (Additional information about how to use this operation or named query.). This is the underlying object with id, value and extensions. The accessor "getNotes" gives direct access to the value
* @param value {@link #comment} (Additional information about how to use this operation or named query.). This is the underlying object with id, value and extensions. The accessor "getComment" gives direct access to the value
*/
public OperationDefinition setNotesElement(StringType value) {
this.notes = value;
public OperationDefinition setCommentElement(StringType value) {
this.comment = value;
return this;
}
/**
* @return Additional information about how to use this operation or named query.
*/
public String getNotes() {
return this.notes == null ? null : this.notes.getValue();
public String getComment() {
return this.comment == null ? null : this.comment.getValue();
}
/**
* @param value Additional information about how to use this operation or named query.
*/
public OperationDefinition setNotes(String value) {
public OperationDefinition setComment(String value) {
if (Utilities.noString(value))
this.notes = null;
this.comment = null;
else {
if (this.notes == null)
this.notes = new StringType();
this.notes.setValue(value);
if (this.comment == null)
this.comment = new StringType();
this.comment.setValue(value);
}
return this;
}
@ -2262,14 +2366,15 @@ public class OperationDefinition extends DomainResource {
childrenList.add(new Property("status", "code", "The status of the profile.", 0, java.lang.Integer.MAX_VALUE, status));
childrenList.add(new Property("kind", "code", "Whether this is an operation or a named query.", 0, java.lang.Integer.MAX_VALUE, kind));
childrenList.add(new Property("experimental", "boolean", "This profile was authored for testing purposes (or education/evaluation/marketing), and is not intended to be used for genuine usage.", 0, java.lang.Integer.MAX_VALUE, experimental));
childrenList.add(new Property("date", "dateTime", "The date this version of the operation definition was published. The date must change when the business version changes, if it does, and it must change if the status code changes. In addition, it should change when the substantive content of the Operation Definition changes.", 0, java.lang.Integer.MAX_VALUE, date));
childrenList.add(new Property("publisher", "string", "The name of the individual or organization that published the operation definition.", 0, java.lang.Integer.MAX_VALUE, publisher));
childrenList.add(new Property("contact", "", "Contacts to assist a user in finding and communicating with the publisher.", 0, java.lang.Integer.MAX_VALUE, contact));
childrenList.add(new Property("date", "dateTime", "The date this version of the operation definition was published. The date must change when the business version changes, if it does, and it must change if the status code changes. In addition, it should change when the substantive content of the Operation Definition changes.", 0, java.lang.Integer.MAX_VALUE, date));
childrenList.add(new Property("description", "string", "A free text natural language description of the profile and its use.", 0, java.lang.Integer.MAX_VALUE, description));
childrenList.add(new Property("useContext", "CodeableConcept", "The content was developed with a focus and intent of supporting the contexts that are listed. These terms may be used to assist with indexing and searching of operation definitions.", 0, java.lang.Integer.MAX_VALUE, useContext));
childrenList.add(new Property("requirements", "string", "Explains why this operation definition is needed and why it's been constrained as it has.", 0, java.lang.Integer.MAX_VALUE, requirements));
childrenList.add(new Property("idempotent", "boolean", "Operations that are idempotent (see [HTTP specification definition of idempotent](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html)) may be invoked by performing an HTTP GET operation instead of a POST.", 0, java.lang.Integer.MAX_VALUE, idempotent));
childrenList.add(new Property("code", "code", "The name used to invoke the operation.", 0, java.lang.Integer.MAX_VALUE, code));
childrenList.add(new Property("notes", "string", "Additional information about how to use this operation or named query.", 0, java.lang.Integer.MAX_VALUE, notes));
childrenList.add(new Property("comment", "string", "Additional information about how to use this operation or named query.", 0, java.lang.Integer.MAX_VALUE, comment));
childrenList.add(new Property("base", "Reference(OperationDefinition)", "Indicates that this operation definition is a constraining profile on the base.", 0, java.lang.Integer.MAX_VALUE, base));
childrenList.add(new Property("system", "boolean", "Indicates whether this operation or named query can be invoked at the system level (e.g. without needing to choose a resource type for the context).", 0, java.lang.Integer.MAX_VALUE, system));
childrenList.add(new Property("type", "code", "Indicates whether this operation or named query can be invoked at the resource type level for any given resource type level (e.g. without needing to choose a resource type for the context).", 0, java.lang.Integer.MAX_VALUE, type));
@ -2291,22 +2396,24 @@ public class OperationDefinition extends DomainResource {
this.kind = new OperationKindEnumFactory().fromType(value); // Enumeration<OperationKind>
else if (name.equals("experimental"))
this.experimental = castToBoolean(value); // BooleanType
else if (name.equals("date"))
this.date = castToDateTime(value); // DateTimeType
else if (name.equals("publisher"))
this.publisher = castToString(value); // StringType
else if (name.equals("contact"))
this.getContact().add((OperationDefinitionContactComponent) value);
else if (name.equals("date"))
this.date = castToDateTime(value); // DateTimeType
else if (name.equals("description"))
this.description = castToString(value); // StringType
else if (name.equals("useContext"))
this.getUseContext().add(castToCodeableConcept(value));
else if (name.equals("requirements"))
this.requirements = castToString(value); // StringType
else if (name.equals("idempotent"))
this.idempotent = castToBoolean(value); // BooleanType
else if (name.equals("code"))
this.code = castToCode(value); // CodeType
else if (name.equals("notes"))
this.notes = castToString(value); // StringType
else if (name.equals("comment"))
this.comment = castToString(value); // StringType
else if (name.equals("base"))
this.base = castToReference(value); // Reference
else if (name.equals("system"))
@ -2341,18 +2448,21 @@ public class OperationDefinition extends DomainResource {
else if (name.equals("experimental")) {
throw new FHIRException("Cannot call addChild on a primitive type OperationDefinition.experimental");
}
else if (name.equals("date")) {
throw new FHIRException("Cannot call addChild on a primitive type OperationDefinition.date");
}
else if (name.equals("publisher")) {
throw new FHIRException("Cannot call addChild on a primitive type OperationDefinition.publisher");
}
else if (name.equals("contact")) {
return addContact();
}
else if (name.equals("date")) {
throw new FHIRException("Cannot call addChild on a primitive type OperationDefinition.date");
}
else if (name.equals("description")) {
throw new FHIRException("Cannot call addChild on a primitive type OperationDefinition.description");
}
else if (name.equals("useContext")) {
return addUseContext();
}
else if (name.equals("requirements")) {
throw new FHIRException("Cannot call addChild on a primitive type OperationDefinition.requirements");
}
@ -2362,8 +2472,8 @@ public class OperationDefinition extends DomainResource {
else if (name.equals("code")) {
throw new FHIRException("Cannot call addChild on a primitive type OperationDefinition.code");
}
else if (name.equals("notes")) {
throw new FHIRException("Cannot call addChild on a primitive type OperationDefinition.notes");
else if (name.equals("comment")) {
throw new FHIRException("Cannot call addChild on a primitive type OperationDefinition.comment");
}
else if (name.equals("base")) {
this.base = new Reference();
@ -2399,18 +2509,23 @@ public class OperationDefinition extends DomainResource {
dst.status = status == null ? null : status.copy();
dst.kind = kind == null ? null : kind.copy();
dst.experimental = experimental == null ? null : experimental.copy();
dst.date = date == null ? null : date.copy();
dst.publisher = publisher == null ? null : publisher.copy();
if (contact != null) {
dst.contact = new ArrayList<OperationDefinitionContactComponent>();
for (OperationDefinitionContactComponent i : contact)
dst.contact.add(i.copy());
};
dst.date = date == null ? null : date.copy();
dst.description = description == null ? null : description.copy();
if (useContext != null) {
dst.useContext = new ArrayList<CodeableConcept>();
for (CodeableConcept i : useContext)
dst.useContext.add(i.copy());
};
dst.requirements = requirements == null ? null : requirements.copy();
dst.idempotent = idempotent == null ? null : idempotent.copy();
dst.code = code == null ? null : code.copy();
dst.notes = notes == null ? null : notes.copy();
dst.comment = comment == null ? null : comment.copy();
dst.base = base == null ? null : base.copy();
dst.system = system == null ? null : system.copy();
if (type != null) {
@ -2440,11 +2555,12 @@ public class OperationDefinition extends DomainResource {
OperationDefinition o = (OperationDefinition) other;
return compareDeep(url, o.url, true) && compareDeep(version, o.version, true) && compareDeep(name, o.name, true)
&& compareDeep(status, o.status, true) && compareDeep(kind, o.kind, true) && compareDeep(experimental, o.experimental, true)
&& compareDeep(publisher, o.publisher, true) && compareDeep(contact, o.contact, true) && compareDeep(date, o.date, true)
&& compareDeep(description, o.description, true) && compareDeep(requirements, o.requirements, true)
&& compareDeep(idempotent, o.idempotent, true) && compareDeep(code, o.code, true) && compareDeep(notes, o.notes, true)
&& compareDeep(base, o.base, true) && compareDeep(system, o.system, true) && compareDeep(type, o.type, true)
&& compareDeep(instance, o.instance, true) && compareDeep(parameter, o.parameter, true);
&& compareDeep(date, o.date, true) && compareDeep(publisher, o.publisher, true) && compareDeep(contact, o.contact, true)
&& compareDeep(description, o.description, true) && compareDeep(useContext, o.useContext, true)
&& compareDeep(requirements, o.requirements, true) && compareDeep(idempotent, o.idempotent, true)
&& compareDeep(code, o.code, true) && compareDeep(comment, o.comment, true) && compareDeep(base, o.base, true)
&& compareDeep(system, o.system, true) && compareDeep(type, o.type, true) && compareDeep(instance, o.instance, true)
&& compareDeep(parameter, o.parameter, true);
}
@Override
@ -2456,21 +2572,16 @@ public class OperationDefinition extends DomainResource {
OperationDefinition o = (OperationDefinition) other;
return compareValues(url, o.url, true) && compareValues(version, o.version, true) && compareValues(name, o.name, true)
&& compareValues(status, o.status, true) && compareValues(kind, o.kind, true) && compareValues(experimental, o.experimental, true)
&& compareValues(publisher, o.publisher, true) && compareValues(date, o.date, true) && compareValues(description, o.description, true)
&& compareValues(date, o.date, true) && compareValues(publisher, o.publisher, true) && compareValues(description, o.description, true)
&& compareValues(requirements, o.requirements, true) && compareValues(idempotent, o.idempotent, true)
&& compareValues(code, o.code, true) && compareValues(notes, o.notes, true) && compareValues(system, o.system, true)
&& compareValues(code, o.code, true) && compareValues(comment, o.comment, true) && compareValues(system, o.system, true)
&& compareValues(type, o.type, true) && compareValues(instance, o.instance, true);
}
public boolean isEmpty() {
return super.isEmpty() && (url == null || url.isEmpty()) && (version == null || version.isEmpty())
&& (name == null || name.isEmpty()) && (status == null || status.isEmpty()) && (kind == null || kind.isEmpty())
&& (experimental == null || experimental.isEmpty()) && (publisher == null || publisher.isEmpty())
&& (contact == null || contact.isEmpty()) && (date == null || date.isEmpty()) && (description == null || description.isEmpty())
&& (requirements == null || requirements.isEmpty()) && (idempotent == null || idempotent.isEmpty())
&& (code == null || code.isEmpty()) && (notes == null || notes.isEmpty()) && (base == null || base.isEmpty())
&& (system == null || system.isEmpty()) && (type == null || type.isEmpty()) && (instance == null || instance.isEmpty())
&& (parameter == null || parameter.isEmpty());
return super.isEmpty() && ca.uhn.fhir.util.ElementUtil.isEmpty( url, version, name, status, kind
, experimental, date, publisher, contact, description, useContext, requirements, idempotent
, code, comment, base, system, type, instance, parameter);
}
@Override
@ -2558,32 +2669,6 @@ public class OperationDefinition extends DomainResource {
*/
public static final ca.uhn.fhir.rest.gclient.TokenClientParam KIND = new ca.uhn.fhir.rest.gclient.TokenClientParam(SP_KIND);
/**
* Search parameter: <b>profile</b>
* <p>
* Description: <b>Profile on the type</b><br>
* Type: <b>reference</b><br>
* Path: <b>OperationDefinition.parameter.profile</b><br>
* </p>
*/
@SearchParamDefinition(name="profile", path="OperationDefinition.parameter.profile", description="Profile on the type", type="reference" )
public static final String SP_PROFILE = "profile";
/**
* <b>Fluent Client</b> search parameter constant for <b>profile</b>
* <p>
* Description: <b>Profile on the type</b><br>
* Type: <b>reference</b><br>
* Path: <b>OperationDefinition.parameter.profile</b><br>
* </p>
*/
public static final ca.uhn.fhir.rest.gclient.ReferenceClientParam PROFILE = new ca.uhn.fhir.rest.gclient.ReferenceClientParam(SP_PROFILE);
/**
* Constant for fluent queries to be used to add include statements. Specifies
* the path value of "<b>OperationDefinition:profile</b>".
*/
public static final ca.uhn.fhir.model.api.Include INCLUDE_PROFILE = new ca.uhn.fhir.model.api.Include("OperationDefinition:profile").toLocked();
/**
* Search parameter: <b>type</b>
* <p>
@ -2624,6 +2709,32 @@ public class OperationDefinition extends DomainResource {
*/
public static final ca.uhn.fhir.rest.gclient.TokenClientParam VERSION = new ca.uhn.fhir.rest.gclient.TokenClientParam(SP_VERSION);
/**
* Search parameter: <b>paramprofile</b>
* <p>
* Description: <b>Profile on the type</b><br>
* Type: <b>reference</b><br>
* Path: <b>OperationDefinition.parameter.profile</b><br>
* </p>
*/
@SearchParamDefinition(name="paramprofile", path="OperationDefinition.parameter.profile", description="Profile on the type", type="reference" )
public static final String SP_PARAMPROFILE = "paramprofile";
/**
* <b>Fluent Client</b> search parameter constant for <b>paramprofile</b>
* <p>
* Description: <b>Profile on the type</b><br>
* Type: <b>reference</b><br>
* Path: <b>OperationDefinition.parameter.profile</b><br>
* </p>
*/
public static final ca.uhn.fhir.rest.gclient.ReferenceClientParam PARAMPROFILE = new ca.uhn.fhir.rest.gclient.ReferenceClientParam(SP_PARAMPROFILE);
/**
* Constant for fluent queries to be used to add include statements. Specifies
* the path value of "<b>OperationDefinition:paramprofile</b>".
*/
public static final ca.uhn.fhir.model.api.Include INCLUDE_PARAMPROFILE = new ca.uhn.fhir.model.api.Include("OperationDefinition:paramprofile").toLocked();
/**
* Search parameter: <b>url</b>
* <p>
@ -2684,6 +2795,26 @@ public class OperationDefinition extends DomainResource {
*/
public static final ca.uhn.fhir.rest.gclient.StringClientParam NAME = new ca.uhn.fhir.rest.gclient.StringClientParam(SP_NAME);
/**
* Search parameter: <b>context</b>
* <p>
* Description: <b>A use context assigned to the operation definition</b><br>
* Type: <b>token</b><br>
* Path: <b>OperationDefinition.useContext</b><br>
* </p>
*/
@SearchParamDefinition(name="context", path="OperationDefinition.useContext", description="A use context assigned to the operation definition", type="token" )
public static final String SP_CONTEXT = "context";
/**
* <b>Fluent Client</b> search parameter constant for <b>context</b>
* <p>
* Description: <b>A use context assigned to the operation definition</b><br>
* Type: <b>token</b><br>
* Path: <b>OperationDefinition.useContext</b><br>
* </p>
*/
public static final ca.uhn.fhir.rest.gclient.TokenClientParam CONTEXT = new ca.uhn.fhir.rest.gclient.TokenClientParam(SP_CONTEXT);
/**
* Search parameter: <b>publisher</b>
* <p>

View File

@ -0,0 +1,186 @@
package ca.uhn.fhir.rest.client;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.InputStream;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.ReaderInputStream;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicStatusLine;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.StringType;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.StringOrListParam;
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;
public class OperationClientDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OperationClientDstu3Test.class);
private FhirContext ourCtx;
private HttpClient ourHttpClient;
private HttpResponse ourHttpResponse;
private IOpClient ourAnnClient;
private ArgumentCaptor<HttpUriRequest> capt;
private IGenericClient ourGenClient;
@Before
public void before() throws Exception {
ourCtx = FhirContext.forDstu3();
ourHttpClient = mock(HttpClient.class, new ReturnsDeepStubs());
ourCtx.getRestfulClientFactory().setHttpClient(ourHttpClient);
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
ourHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
Parameters outParams = new Parameters();
outParams.addParameter().setName("FOO");
final String retVal = ourCtx.newXmlParser().encodeResourceToString(outParams);
capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(ourHttpClient.execute(capt.capture())).thenReturn(ourHttpResponse);
when(ourHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(ourHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(ourHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(retVal), Charset.forName("UTF-8"));
}
});
ourAnnClient = ourCtx.newRestfulClient(IOpClient.class, "http://foo");
ourGenClient = ourCtx.newRestfulGenericClient("http://foo");
}
@Test
public void testNonRepeatingGenericUsingParameters() throws Exception {
ourGenClient
.operation()
.onServer()
.named("nonrepeating")
.withSearchParameter(Parameters.class, "valstr", new StringParam("str"))
.andSearchParameter("valtok", new TokenParam("sys2", "val2"))
.execute();
Parameters response = ourAnnClient.nonrepeating(new StringParam("str"), new TokenParam("sys", "val"));
assertEquals("FOO", response.getParameter().get(0).getName());
HttpPost value = (HttpPost) capt.getAllValues().get(0);
String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent());
IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent());
ourLog.info(requestBody);
Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody);
assertEquals("http://foo/$nonrepeating", value.getURI().toASCIIString());
assertEquals(2, request.getParameter().size());
assertEquals("valstr", request.getParameter().get(0).getName());
assertEquals("str", ((StringType) request.getParameter().get(0).getValue()).getValue());
assertEquals("valtok", request.getParameter().get(1).getName());
assertEquals("sys2|val2", ((StringType) request.getParameter().get(1).getValue()).getValue());
}
@Test
public void testNonRepeatingGenericUsingUrl() throws Exception {
ourGenClient
.operation()
.onServer()
.named("nonrepeating")
.withSearchParameter(Parameters.class, "valstr", new StringParam("str"))
.andSearchParameter("valtok", new TokenParam("sys2", "val2"))
.useHttpGet()
.execute();
Parameters response = ourAnnClient.nonrepeating(new StringParam("str"), new TokenParam("sys", "val"));
assertEquals("FOO", response.getParameter().get(0).getName());
HttpGet value = (HttpGet) capt.getAllValues().get(0);
assertEquals("http://foo/$nonrepeating?valstr=str&valtok=sys2%7Cval2", value.getURI().toASCIIString());
}
@Test
public void testNonRepeatingUsingParameters() throws Exception {
Parameters response = ourAnnClient.nonrepeating(new StringParam("str"), new TokenParam("sys", "val"));
assertEquals("FOO", response.getParameter().get(0).getName());
HttpPost value = (HttpPost) capt.getAllValues().get(0);
String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent());
IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent());
ourLog.info(requestBody);
Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody);
assertEquals("http://foo/$nonrepeating", value.getURI().toASCIIString());
assertEquals(2, request.getParameter().size());
assertEquals("valstr", request.getParameter().get(0).getName());
assertEquals("str", ((StringType) request.getParameter().get(0).getValue()).getValue());
assertEquals("valtok", request.getParameter().get(1).getName());
assertEquals("sys|val", ((StringType) request.getParameter().get(1).getValue()).getValue());
}
public interface IOpClient extends IBasicClient {
@Operation(name = "$andlist", idempotent = true)
public Parameters andlist(
//@formatter:off
@OperationParam(name="valstr", max=10) StringAndListParam theValStr,
@OperationParam(name="valtok", max=10) TokenAndListParam theValTok
//@formatter:on
);
@Operation(name = "$andlist-withnomax", idempotent = true)
public Parameters andlistWithNoMax(
//@formatter:off
@OperationParam(name="valstr") StringAndListParam theValStr,
@OperationParam(name="valtok") TokenAndListParam theValTok
//@formatter:on
);
@Operation(name = "$nonrepeating", idempotent = true)
public Parameters nonrepeating(
//@formatter:off
@OperationParam(name="valstr") StringParam theValStr,
@OperationParam(name="valtok") TokenParam theValTok
//@formatter:on
);
@Operation(name = "$orlist", idempotent = true)
public Parameters orlist(
//@formatter:off
@OperationParam(name="valstr", max=10) List<StringOrListParam> theValStr,
@OperationParam(name="valtok", max=10) List<TokenOrListParam> theValTok
//@formatter:on
);
@Operation(name = "$orlist-withnomax", idempotent = true)
public Parameters orlistWithNoMax(
//@formatter:off
@OperationParam(name="valstr") List<StringOrListParam> theValStr,
@OperationParam(name="valtok") List<TokenOrListParam> theValTok
//@formatter:on
);
}
}

View File

@ -0,0 +1,491 @@
package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
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.hapi.rest.server.ServerConformanceProvider;
import org.hl7.fhir.dstu3.model.Conformance;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.OperationDefinition;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.StringType;
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.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.StringOrListParam;
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.param.TokenParamModifier;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.UrlUtil;
public class OperationServerWithSearchParamTypesDstu3Test {
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx;
private static String ourLastMethod;
private static List<StringOrListParam> ourLastParamValStr;
private static List<TokenOrListParam> ourLastParamValTok;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OperationServerWithSearchParamTypesDstu3Test.class);
private static int ourPort;
private static Server ourServer;
@Before
public void before() {
ourLastMethod = "";
ourLastParamValStr = null;
ourLastParamValTok = null;
}
private HttpServletRequest createHttpServletRequest() {
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getRequestURI()).thenReturn("/FhirStorm/fhir/Patient/_search");
when(req.getServletPath()).thenReturn("/fhir");
when(req.getRequestURL()).thenReturn(new StringBuffer().append("http://fhirstorm.dyndns.org:8080/FhirStorm/fhir/Patient/_search"));
when(req.getContextPath()).thenReturn("/FhirStorm");
return req;
}
private ServletConfig createServletConfig() {
ServletConfig sc = mock(ServletConfig.class);
when(sc.getServletContext()).thenReturn(null);
return sc;
}
@Test
public void testAndListWithParameters() throws Exception {
Parameters p = new Parameters();
p.addParameter().setName("valstr").setValue(new StringType("VALSTR1A,VALSTR1B"));
p.addParameter().setName("valstr").setValue(new StringType("VALSTR2A,VALSTR2B"));
p.addParameter().setName("valtok").setValue(new StringType("VALTOK1A|VALTOK1B"));
p.addParameter().setName("valtok").setValue(new StringType("VALTOK2A|VALTOK2B"));
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$andlist");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
ourLog.info(response);
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(2, ourLastParamValStr.size());
assertEquals(2, ourLastParamValStr.get(0).getValuesAsQueryTokens().size());
assertEquals("VALSTR1A", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALSTR1B", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(1).getValue());
assertEquals("VALSTR2A", ourLastParamValStr.get(1).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALSTR2B", ourLastParamValStr.get(1).getValuesAsQueryTokens().get(1).getValue());
assertEquals(2, ourLastParamValTok.size());
assertEquals(1, ourLastParamValTok.get(0).getValuesAsQueryTokens().size());
assertEquals("VALTOK1A", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOK1B", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALTOK2A", ourLastParamValTok.get(1).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOK2B", ourLastParamValTok.get(1).getValuesAsQueryTokens().get(0).getValue());
assertEquals("type $orlist", ourLastMethod);
}
@Test
public void testAndListWithUrl() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$andlist?valstr=VALSTR1A,VALSTR1B&valstr=VALSTR2A,VALSTR2B&valtok=" + UrlUtil.escape("VALTOK1A|VALTOK1B") + "&valtok=" + UrlUtil.escape("VALTOK2A|VALTOK2B"));
HttpResponse status = ourClient.execute(httpGet);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
ourLog.info(response);
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(2, ourLastParamValStr.size());
assertEquals(2, ourLastParamValStr.get(0).getValuesAsQueryTokens().size());
assertEquals("VALSTR1A", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALSTR1B", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(1).getValue());
assertEquals("VALSTR2A", ourLastParamValStr.get(1).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALSTR2B", ourLastParamValStr.get(1).getValuesAsQueryTokens().get(1).getValue());
assertEquals(2, ourLastParamValTok.size());
assertEquals(1, ourLastParamValTok.get(0).getValuesAsQueryTokens().size());
assertEquals("VALTOK1A", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOK1B", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALTOK2A", ourLastParamValTok.get(1).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOK2B", ourLastParamValTok.get(1).getValuesAsQueryTokens().get(0).getValue());
assertEquals("type $orlist", ourLastMethod);
}
@Test
public void testGenerateConformance() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
rs.setProviders(new PatientProvider());
ServerConformanceProvider sc = new ServerConformanceProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
Conformance conformance = sc.getServerConformance(createHttpServletRequest());
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
//@formatter:off
assertThat(conf, stringContainsInOrder(
"<type value=\"Patient\"/>",
"<operation>",
"<name value=\"$andlist\"/>",
"<operation>"
));
assertThat(conf, stringContainsInOrder(
"<type value=\"Patient\"/>",
"<operation>",
"<name value=\"$nonrepeating\"/>"
));
assertThat(conf, stringContainsInOrder(
"<type value=\"Patient\"/>",
"<operation>",
"<name value=\"$orlist\"/>"
));
//@formatter:on
/*
* Check the operation definitions themselves
*/
OperationDefinition andListDef = sc.readOperationDefinition(new IdType("OperationDefinition/andlist"));
String def = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(andListDef);
ourLog.info(def);
//@formatter:off
assertThat(def, stringContainsInOrder(
"<parameter>",
"<name value=\"valtok\"/>",
"<use value=\"in\"/>",
"<min value=\"0\"/>",
"<max value=\"10\"/>",
"<type value=\"string\"/>",
"<searchType value=\"token\"/>",
"</parameter>"
));
//@formatter:on
andListDef = sc.readOperationDefinition(new IdType("OperationDefinition/andlist-withnomax"));
def = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(andListDef);
ourLog.info(def);
//@formatter:off
assertThat(def, stringContainsInOrder(
"<parameter>",
"<name value=\"valtok\"/>",
"<use value=\"in\"/>",
"<min value=\"0\"/>",
"<max value=\"*\"/>",
"<type value=\"string\"/>",
"<searchType value=\"token\"/>",
"</parameter>"
));
//@formatter:on
OperationDefinition orListDef = sc.readOperationDefinition(new IdType("OperationDefinition/orlist"));
def = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(orListDef);
ourLog.info(def);
//@formatter:off
assertThat(def, stringContainsInOrder(
"<parameter>",
"<name value=\"valtok\"/>",
"<use value=\"in\"/>",
"<min value=\"0\"/>",
"<max value=\"10\"/>",
"<type value=\"string\"/>",
"<searchType value=\"token\"/>",
"</parameter>"
));
//@formatter:on
orListDef = sc.readOperationDefinition(new IdType("OperationDefinition/orlist-withnomax"));
def = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(orListDef);
ourLog.info(def);
//@formatter:off
assertThat(def, stringContainsInOrder(
"<parameter>",
"<name value=\"valtok\"/>",
"<use value=\"in\"/>",
"<min value=\"0\"/>",
"<max value=\"*\"/>",
"<type value=\"string\"/>",
"<searchType value=\"token\"/>",
"</parameter>"
));
//@formatter:on
}
@Test
public void testNonRepeatingWithParams() throws Exception {
Parameters p = new Parameters();
p.addParameter().setName("valstr").setValue(new StringType("VALSTR"));
p.addParameter().setName("valtok").setValue(new StringType("VALTOKA|VALTOKB"));
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$nonrepeating");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
ourLog.info(response);
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(1, ourLastParamValStr.size());
assertEquals(1, ourLastParamValStr.get(0).getValuesAsQueryTokens().size());
assertEquals("VALSTR", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals(1, ourLastParamValTok.size());
assertEquals(1, ourLastParamValTok.get(0).getValuesAsQueryTokens().size());
assertEquals("VALTOKA", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOKB", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("type $nonrepeating", ourLastMethod);
}
@Test
public void testNonRepeatingWithUrl() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$nonrepeating?valstr=VALSTR&valtok=" + UrlUtil.escape("VALTOKA|VALTOKB"));
HttpResponse status = ourClient.execute(httpGet);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
ourLog.info(response);
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(1, ourLastParamValStr.size());
assertEquals(1, ourLastParamValStr.get(0).getValuesAsQueryTokens().size());
assertEquals("VALSTR", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals(1, ourLastParamValTok.size());
assertEquals(1, ourLastParamValTok.get(0).getValuesAsQueryTokens().size());
assertEquals("VALTOKA", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOKB", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("type $nonrepeating", ourLastMethod);
}
@Test
public void testNonRepeatingWithUrlQualified() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$nonrepeating?valstr:exact=VALSTR&valtok:not=" + UrlUtil.escape("VALTOKA|VALTOKB"));
HttpResponse status = ourClient.execute(httpGet);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
ourLog.info(response);
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(1, ourLastParamValStr.size());
assertEquals(1, ourLastParamValStr.get(0).getValuesAsQueryTokens().size());
assertEquals("VALSTR", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(0).getValue());
assertTrue(ourLastParamValStr.get(0).getValuesAsQueryTokens().get(0).isExact());
assertEquals(1, ourLastParamValTok.size());
assertEquals(1, ourLastParamValTok.get(0).getValuesAsQueryTokens().size());
assertEquals("VALTOKA", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOKB", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals(TokenParamModifier.NOT, ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getModifier());
assertEquals("type $nonrepeating", ourLastMethod);
}
@Test
public void testOrListWithParameters() throws Exception {
Parameters p = new Parameters();
p.addParameter().setName("valstr").setValue(new StringType("VALSTR1A,VALSTR1B"));
p.addParameter().setName("valstr").setValue(new StringType("VALSTR2A,VALSTR2B"));
p.addParameter().setName("valtok").setValue(new StringType("VALTOK1A|VALTOK1B"));
p.addParameter().setName("valtok").setValue(new StringType("VALTOK2A|VALTOK2B"));
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$orlist");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
ourLog.info(response);
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(2, ourLastParamValStr.size());
assertEquals(2, ourLastParamValStr.get(0).getValuesAsQueryTokens().size());
assertEquals("VALSTR1A", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALSTR1B", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(1).getValue());
assertEquals("VALSTR2A", ourLastParamValStr.get(1).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALSTR2B", ourLastParamValStr.get(1).getValuesAsQueryTokens().get(1).getValue());
assertEquals(2, ourLastParamValTok.size());
assertEquals(1, ourLastParamValTok.get(0).getValuesAsQueryTokens().size());
assertEquals("VALTOK1A", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOK1B", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALTOK2A", ourLastParamValTok.get(1).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOK2B", ourLastParamValTok.get(1).getValuesAsQueryTokens().get(0).getValue());
assertEquals("type $orlist", ourLastMethod);
}
@Test
public void testOrListWithUrl() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$orlist?valstr=VALSTR1A,VALSTR1B&valstr=VALSTR2A,VALSTR2B&valtok=" + UrlUtil.escape("VALTOK1A|VALTOK1B") + "&valtok=" + UrlUtil.escape("VALTOK2A|VALTOK2B"));
HttpResponse status = ourClient.execute(httpGet);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
ourLog.info(response);
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(2, ourLastParamValStr.size());
assertEquals(2, ourLastParamValStr.get(0).getValuesAsQueryTokens().size());
assertEquals("VALSTR1A", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALSTR1B", ourLastParamValStr.get(0).getValuesAsQueryTokens().get(1).getValue());
assertEquals("VALSTR2A", ourLastParamValStr.get(1).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALSTR2B", ourLastParamValStr.get(1).getValuesAsQueryTokens().get(1).getValue());
assertEquals(2, ourLastParamValTok.size());
assertEquals(1, ourLastParamValTok.get(0).getValuesAsQueryTokens().size());
assertEquals("VALTOK1A", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOK1B", ourLastParamValTok.get(0).getValuesAsQueryTokens().get(0).getValue());
assertEquals("VALTOK2A", ourLastParamValTok.get(1).getValuesAsQueryTokens().get(0).getSystem());
assertEquals("VALTOK2B", ourLastParamValTok.get(1).getValuesAsQueryTokens().get(0).getValue());
assertEquals("type $orlist", ourLastMethod);
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourCtx = FhirContext.forDstu3();
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
servlet.setPagingProvider(new FifoMemoryPagingProvider(10).setDefaultPageSize(2));
servlet.setFhirContext(ourCtx);
servlet.setResourceProviders(new 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();
}
public static class PatientProvider implements IResourceProvider {
@Operation(name = "$andlist", idempotent = true)
public Parameters andlist(
//@formatter:off
@OperationParam(name="valstr", max=10) StringAndListParam theValStr,
@OperationParam(name="valtok", max=10) TokenAndListParam theValTok
//@formatter:on
) {
ourLastMethod = "type $orlist";
ourLastParamValStr = theValStr.getValuesAsQueryTokens();
ourLastParamValTok = theValTok.getValuesAsQueryTokens();
return createEmptyParams();
}
@Operation(name = "$andlist-withnomax", idempotent = true)
public Parameters andlistWithNoMax(
//@formatter:off
@OperationParam(name="valstr") StringAndListParam theValStr,
@OperationParam(name="valtok") TokenAndListParam theValTok
//@formatter:on
) {
ourLastMethod = "type $orlist";
ourLastParamValStr = theValStr.getValuesAsQueryTokens();
ourLastParamValTok = theValTok.getValuesAsQueryTokens();
return createEmptyParams();
}
/**
* Just so we have something to return
*/
private Parameters createEmptyParams() {
Parameters retVal = new Parameters();
retVal.setId("100");
return retVal;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return Patient.class;
}
@Operation(name = "$nonrepeating", idempotent = true)
public Parameters nonrepeating(
//@formatter:off
@OperationParam(name="valstr") StringParam theValStr,
@OperationParam(name="valtok") TokenParam theValTok
//@formatter:on
) {
ourLastMethod = "type $nonrepeating";
ourLastParamValStr = Collections.singletonList(new StringOrListParam().add(theValStr));
ourLastParamValTok = Collections.singletonList(new TokenOrListParam().add(theValTok));
return createEmptyParams();
}
@Operation(name = "$orlist", idempotent = true)
public Parameters orlist(
//@formatter:off
@OperationParam(name="valstr", max=10) List<StringOrListParam> theValStr,
@OperationParam(name="valtok", max=10) List<TokenOrListParam> theValTok
//@formatter:on
) {
ourLastMethod = "type $orlist";
ourLastParamValStr = theValStr;
ourLastParamValTok = theValTok;
return createEmptyParams();
}
@Operation(name = "$orlist-withnomax", idempotent = true)
public Parameters orlistWithNoMax(
//@formatter:off
@OperationParam(name="valstr") List<StringOrListParam> theValStr,
@OperationParam(name="valtok") List<TokenOrListParam> theValTok
//@formatter:on
) {
ourLastMethod = "type $orlist";
ourLastParamValStr = theValStr;
ourLastParamValTok = theValTok;
return createEmptyParams();
}
}
}

View File

@ -338,6 +338,29 @@
coded fields which use example bindings. Thanks
to GitHub user Ricq for reporting!
</action>
<action type="add">
<![CDATA[
Operations methods defined using
<code>@Operation</code> will now infer the maximum number of repetitions
of their parameters by the type of the parameter. Previously if
a default <code>max()</code> value was not specified in the
<code>@OperationParam</code> annotation on a parameter, the maximum
was assumed to be 1. Now, if a max value is not explicitly specified
and the type of the parameter is a basic type (e.g. <code>StringDt</code>) the
max will be 1. If the parameter is a collection type (e.g. <code>List&lt;StringDt&gt;</code>)
the max will be *
]]>
</action>
<action type="add" issue="317">
<![CDATA[
Operation methods defined using
<code>@Operation</code>
may now use search parameter types, such as
<code>TokenParam</code> and
<code>TokenAndListParam</code> as values. Thanks to
Christian Ohr for reporting!
]]>
</action>
</release>
<release version="1.4" date="2016-02-04">
<action type="add">

View File

@ -1649,6 +1649,51 @@
</subsection>
<subsection name="Using Search Parameter Types">
<p>
FHIR allows operation parameters to be of a
<a href="http://hl7.org/fhir/search.html#ptypes">Search parameter type</a>
(e.g. token) instead of a FHIR datatype (e.g. Coding).
</p>
<p>
To use a search parameter type, any of the search parameter
types listed in
<a href="./doc_rest_operations.html#Type_Level_-_Search">Search</a>
may be used. For example, the following is a simple operation method declaration
using search parameters:
</p>
<macro name="snippet">
<param name="id" value="searchParamBasic" />
<param name="file"
value="examples/src/main/java/example/ServerOperations.java" />
</macro>
<p>
Example URL to invoke this operation (HTTP request body is Parameters resource):
<br />
<code>http://fhir.example.com/$find-matches?date=2011-01-02&amp;code=http://system|value</code>
</p>
<p>
It is also fine to use collection types for search parameter types
if you want to be able to accept multiple values. For example,
a <code>List&lt;TokenParam&gt;</code> could be used if you want
to allow multiple repetitions of a given token parameter (this is
analogous to the "AND" semantics in a search).
A <code>TokenOrListParam</code> could be used if you want to allow
multiple values within a single repetition, separated by comma (this
is analogous to "OR" semantics in a search).
</p>
<p>For example:</p>
<macro name="snippet">
<param name="id" value="searchParamAdvanced" />
<param name="file"
value="examples/src/main/java/example/ServerOperations.java" />
</macro>
</subsection>
<subsection name="Server Operations">
<p>