Fix #371 - STU3 server and client should use new sort parameter style
This commit is contained in:
parent
f4b9c6423c
commit
d966190f9e
|
@ -34,6 +34,7 @@ public class SortSpec {
|
|||
* Constructor
|
||||
*/
|
||||
public SortSpec() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -70,6 +70,8 @@ import ca.uhn.fhir.parser.DataFormatException;
|
|||
import ca.uhn.fhir.parser.IParser;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.api.PreferReturnEnum;
|
||||
import ca.uhn.fhir.rest.api.SortOrderEnum;
|
||||
import ca.uhn.fhir.rest.api.SortSpec;
|
||||
import ca.uhn.fhir.rest.api.SummaryEnum;
|
||||
import ca.uhn.fhir.rest.client.api.IHttpClient;
|
||||
import ca.uhn.fhir.rest.client.api.IHttpRequest;
|
||||
|
@ -131,6 +133,7 @@ import ca.uhn.fhir.rest.method.OperationMethodBinding;
|
|||
import ca.uhn.fhir.rest.method.ReadMethodBinding;
|
||||
import ca.uhn.fhir.rest.method.SearchMethodBinding;
|
||||
import ca.uhn.fhir.rest.method.SearchStyleEnum;
|
||||
import ca.uhn.fhir.rest.method.SortParameter;
|
||||
import ca.uhn.fhir.rest.method.TransactionMethodBinding;
|
||||
import ca.uhn.fhir.rest.method.ValidateMethodBindingDstu1;
|
||||
import ca.uhn.fhir.rest.method.ValidateMethodBindingDstu2;
|
||||
|
@ -233,8 +236,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
return delete(theType, new IdDt(theId));
|
||||
}
|
||||
|
||||
private <T extends IBaseResource> T doReadOrVRead(final Class<T> theType, IIdType theId, boolean theVRead, ICallable<T> theNotModifiedHandler, String theIfVersionMatches, Boolean thePrettyPrint,
|
||||
SummaryEnum theSummary, EncodingEnum theEncoding, Set<String> theSubsetElements) {
|
||||
private <T extends IBaseResource> T doReadOrVRead(final Class<T> theType, IIdType theId, boolean theVRead, ICallable<T> theNotModifiedHandler, String theIfVersionMatches, Boolean thePrettyPrint, SummaryEnum theSummary, EncodingEnum theEncoding, Set<String> theSubsetElements) {
|
||||
String resName = toResourceName(theType);
|
||||
IIdType id = theId;
|
||||
if (!id.hasBaseUrl()) {
|
||||
|
@ -264,7 +266,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
}
|
||||
|
||||
boolean allowHtmlResponse = (theSummary == SummaryEnum.TEXT) || (theSummary == null && getSummary() == SummaryEnum.TEXT);
|
||||
ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theType, (Class<? extends IBaseResource>)null, id, allowHtmlResponse);
|
||||
ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theType, (Class<? extends IBaseResource>) null, id, allowHtmlResponse);
|
||||
|
||||
if (theNotModifiedHandler == null) {
|
||||
return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements);
|
||||
|
@ -596,44 +598,48 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
}
|
||||
}
|
||||
|
||||
private abstract class BaseClientExecutable<T extends IClientExecutable<?, ?>, Y> implements IClientExecutable<T, Y> {
|
||||
|
||||
private List<Class<? extends IBaseResource>> myPreferResponseTypes;
|
||||
|
||||
public List<Class<? extends IBaseResource>> getPreferResponseTypes() {
|
||||
return myPreferResponseTypes;
|
||||
}
|
||||
|
||||
public List<Class<? extends IBaseResource>> getPreferResponseTypes(Class<? extends IBaseResource> theDefault) {
|
||||
if (myPreferResponseTypes != null) {
|
||||
return myPreferResponseTypes;
|
||||
private static String validateAndEscapeConditionalUrl(String theSearchUrl) {
|
||||
Validate.notBlank(theSearchUrl, "Conditional URL can not be blank/null");
|
||||
StringBuilder b = new StringBuilder();
|
||||
boolean haveHadQuestionMark = false;
|
||||
for (int i = 0; i < theSearchUrl.length(); i++) {
|
||||
char nextChar = theSearchUrl.charAt(i);
|
||||
if (!haveHadQuestionMark) {
|
||||
if (nextChar == '?') {
|
||||
haveHadQuestionMark = true;
|
||||
} else if (!Character.isLetter(nextChar)) {
|
||||
throw new IllegalArgumentException("Conditional URL must be in the format \"[ResourceType]?[Params]\" and must not have a base URL - Found: " + theSearchUrl);
|
||||
}
|
||||
b.append(nextChar);
|
||||
} else {
|
||||
return toTypeList(theDefault);
|
||||
switch (nextChar) {
|
||||
case '|':
|
||||
case '?':
|
||||
case '$':
|
||||
case ':':
|
||||
b.append(UrlUtil.escape(Character.toString(nextChar)));
|
||||
break;
|
||||
default:
|
||||
b.append(nextChar);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public T preferResponseType(Class<? extends IBaseResource> theClass) {
|
||||
myPreferResponseTypes = null;
|
||||
if (theClass != null) {
|
||||
myPreferResponseTypes = new ArrayList<Class<? extends IBaseResource>>();
|
||||
myPreferResponseTypes.add(theClass);
|
||||
}
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public T preferResponseTypes(List<Class<? extends IBaseResource>> theClass) {
|
||||
myPreferResponseTypes = theClass;
|
||||
return (T) this;
|
||||
}
|
||||
private abstract class BaseClientExecutable<T extends IClientExecutable<?, ?>, Y> implements IClientExecutable<T, Y> {
|
||||
|
||||
protected EncodingEnum myParamEncoding;
|
||||
|
||||
private List<Class<? extends IBaseResource>> myPreferResponseTypes;
|
||||
|
||||
protected Boolean myPrettyPrint;
|
||||
|
||||
private boolean myQueryLogRequestAndResponse;
|
||||
|
||||
private HashSet<String> mySubsetElements;
|
||||
|
||||
protected SummaryEnum mySummaryMode;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -643,6 +649,17 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
return (T) this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public T elementsSubset(String... theElements) {
|
||||
if (theElements != null && theElements.length > 0) {
|
||||
mySubsetElements = new HashSet<String>(Arrays.asList(theElements));
|
||||
} else {
|
||||
mySubsetElements = null;
|
||||
}
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public T encodedJson() {
|
||||
|
@ -661,6 +678,18 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
return myParamEncoding;
|
||||
}
|
||||
|
||||
public List<Class<? extends IBaseResource>> getPreferResponseTypes() {
|
||||
return myPreferResponseTypes;
|
||||
}
|
||||
|
||||
public List<Class<? extends IBaseResource>> getPreferResponseTypes(Class<? extends IBaseResource> theDefault) {
|
||||
if (myPreferResponseTypes != null) {
|
||||
return myPreferResponseTypes;
|
||||
} else {
|
||||
return toTypeList(theDefault);
|
||||
}
|
||||
}
|
||||
|
||||
protected HashSet<String> getSubsetElements() {
|
||||
return mySubsetElements;
|
||||
}
|
||||
|
@ -692,19 +721,26 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public T prettyPrint() {
|
||||
myPrettyPrint = true;
|
||||
public T preferResponseType(Class<? extends IBaseResource> theClass) {
|
||||
myPreferResponseTypes = null;
|
||||
if (theClass != null) {
|
||||
myPreferResponseTypes = new ArrayList<Class<? extends IBaseResource>>();
|
||||
myPreferResponseTypes.add(theClass);
|
||||
}
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public T elementsSubset(String... theElements) {
|
||||
if (theElements != null && theElements.length > 0) {
|
||||
mySubsetElements = new HashSet<String>(Arrays.asList(theElements));
|
||||
} else {
|
||||
mySubsetElements = null;
|
||||
}
|
||||
public T preferResponseTypes(List<Class<? extends IBaseResource>> theClass) {
|
||||
myPreferResponseTypes = theClass;
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public T prettyPrint() {
|
||||
myPrettyPrint = true;
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
|
@ -736,7 +772,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
}
|
||||
}
|
||||
|
||||
private class CreateInternal extends BaseClientExecutable<ICreateTyped, MethodOutcome>implements ICreate, ICreateTyped, ICreateWithQuery, ICreateWithQueryTyped {
|
||||
private class CreateInternal extends BaseClientExecutable<ICreateTyped, MethodOutcome> implements ICreate, ICreateTyped, ICreateWithQuery, ICreateWithQueryTyped {
|
||||
|
||||
private CriterionList myCriterionList;
|
||||
private String myId;
|
||||
|
@ -856,7 +892,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
|
||||
}
|
||||
|
||||
private class DeleteInternal extends BaseClientExecutable<IDeleteTyped, BaseOperationOutcome>implements IDelete, IDeleteTyped, IDeleteWithQuery, IDeleteWithQueryTyped {
|
||||
private class DeleteInternal extends BaseClientExecutable<IDeleteTyped, BaseOperationOutcome> implements IDelete, IDeleteTyped, IDeleteWithQuery, IDeleteWithQueryTyped {
|
||||
|
||||
private CriterionList myCriterionList;
|
||||
private IIdType myId;
|
||||
|
@ -921,6 +957,14 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDeleteWithQuery resourceConditionalByType(Class<? extends IBaseResource> theResourceType) {
|
||||
Validate.notNull(theResourceType, "theResourceType can not be null");
|
||||
myCriterionList = new CriterionList();
|
||||
myResourceType = myContext.getResourceDefinition(theResourceType).getName();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDeleteWithQuery resourceConditionalByType(String theResourceType) {
|
||||
Validate.notBlank(theResourceType, "theResourceType can not be blank/null");
|
||||
|
@ -943,14 +987,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
myCriterionList.add((ICriterionInternal) theCriterion);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDeleteWithQuery resourceConditionalByType(Class<? extends IBaseResource> theResourceType) {
|
||||
Validate.notNull(theResourceType, "theResourceType can not be null");
|
||||
myCriterionList = new CriterionList();
|
||||
myResourceType = myContext.getResourceDefinition(theResourceType).getName();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
|
@ -977,7 +1013,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private class GetPageInternal extends BaseClientExecutable<IGetPageTyped<Object>, Object>implements IGetPageTyped<Object> {
|
||||
private class GetPageInternal extends BaseClientExecutable<IGetPageTyped<Object>, Object> implements IGetPageTyped<Object> {
|
||||
|
||||
private Class<? extends IBaseBundle> myBundleType;
|
||||
private String myUrl;
|
||||
|
@ -1007,7 +1043,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
|
||||
}
|
||||
|
||||
private class GetTagsInternal extends BaseClientExecutable<IGetTags, TagList>implements IGetTags {
|
||||
private class GetTagsInternal extends BaseClientExecutable<IGetTags, TagList> implements IGetTags {
|
||||
|
||||
private String myId;
|
||||
private String myResourceName;
|
||||
|
@ -1403,9 +1439,59 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
private IIdType myId;
|
||||
private String myOperationName;
|
||||
private IBaseParameters myParameters;
|
||||
private RuntimeResourceDefinition myParametersDef;
|
||||
private Class<? extends IBaseResource> myType;
|
||||
private boolean myUseHttpGet;
|
||||
private RuntimeResourceDefinition myParametersDef;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void addParam(String theName, IBase theValue) {
|
||||
BaseRuntimeChildDefinition parameterChild = myParametersDef.getChildByName("parameter");
|
||||
BaseRuntimeElementCompositeDefinition<?> parameterElem = (BaseRuntimeElementCompositeDefinition<?>) parameterChild.getChildByName("parameter");
|
||||
|
||||
IBase parameter = parameterElem.newInstance();
|
||||
parameterChild.getMutator().addValue(myParameters, parameter);
|
||||
|
||||
IPrimitiveType<String> name = (IPrimitiveType<String>) myContext.getElementDefinition("string").newInstance();
|
||||
name.setValue(theName);
|
||||
parameterElem.getChildByName("name").getMutator().setValue(parameter, name);
|
||||
|
||||
if (theValue instanceof IBaseDatatype) {
|
||||
BaseRuntimeElementDefinition<?> datatypeDef = myContext.getElementDefinition(theValue.getClass());
|
||||
if (datatypeDef instanceof IRuntimeDatatypeDefinition) {
|
||||
Class<? extends IBaseDatatype> profileOf = ((IRuntimeDatatypeDefinition) datatypeDef).getProfileOf();
|
||||
if (profileOf != null) {
|
||||
datatypeDef = myContext.getElementDefinition(profileOf);
|
||||
}
|
||||
}
|
||||
String childElementName = "value" + StringUtils.capitalize(datatypeDef.getName());
|
||||
BaseRuntimeChildDefinition childByName = parameterElem.getChildByName(childElementName);
|
||||
childByName.getMutator().setValue(parameter, theValue);
|
||||
} else if (theValue instanceof IBaseResource) {
|
||||
parameterElem.getChildByName("resource").getMutator().setValue(parameter, theValue);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Don't know how to handle parameter of type " + theValue.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
private void addParam(String theName, IQueryParameterType theValue) {
|
||||
IPrimitiveType<?> stringType = ParametersUtil.createString(myContext, theValue.getValueAsQueryToken(myContext));
|
||||
addParam(theName, stringType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IOperationUntypedWithInputAndPartialOutput andParameter(String theName, IBase theValue) {
|
||||
Validate.notEmpty(theName, "theName must not be null");
|
||||
Validate.notNull(theValue, "theValue must not be null");
|
||||
addParam(theName, theValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IOperationUntypedWithInputAndPartialOutput andSearchParameter(String theName, IQueryParameterType theValue) {
|
||||
addParam(theName, theValue);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
|
@ -1427,7 +1513,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
|
||||
ResourceResponseHandler handler = new ResourceResponseHandler();
|
||||
handler.setPreferResponseTypes(getPreferResponseTypes(myType));
|
||||
|
||||
|
||||
Object retVal = invoke(null, handler, invocation);
|
||||
if (myContext.getResourceDefinition((IBaseResource) retVal).getName().equals("Parameters")) {
|
||||
return retVal;
|
||||
|
@ -1486,21 +1572,12 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
throw new IllegalArgumentException("theOutputParameterType must refer to a HAPI FHIR Resource type: " + theOutputParameterType.getName());
|
||||
}
|
||||
if (!"Parameters".equals(def.getName())) {
|
||||
throw new IllegalArgumentException("theOutputParameterType must refer to a HAPI FHIR Resource type for a resource named " + "Parameters" + " - " + theOutputParameterType.getName()
|
||||
+ " is a resource named: " + def.getName());
|
||||
throw new IllegalArgumentException("theOutputParameterType must refer to a HAPI FHIR Resource type for a resource named " + "Parameters" + " - " + theOutputParameterType.getName() + " is a resource named: " + def.getName());
|
||||
}
|
||||
myParameters = (IBaseParameters) def.newInstance();
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
@Override
|
||||
public IOperationUntypedWithInput withParameters(IBaseParameters theParameters) {
|
||||
Validate.notNull(theParameters, "theParameters can not be null");
|
||||
myParameters = theParameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T extends IBaseParameters> IOperationUntypedWithInputAndPartialOutput<T> withParameter(Class<T> theParameterType, String theName, IBase theValue) {
|
||||
|
@ -1516,48 +1593,11 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void addParam(String theName, IBase theValue) {
|
||||
BaseRuntimeChildDefinition parameterChild = myParametersDef.getChildByName("parameter");
|
||||
BaseRuntimeElementCompositeDefinition<?> parameterElem = (BaseRuntimeElementCompositeDefinition<?>) parameterChild.getChildByName("parameter");
|
||||
|
||||
IBase parameter = parameterElem.newInstance();
|
||||
parameterChild.getMutator().addValue(myParameters, parameter);
|
||||
|
||||
IPrimitiveType<String> name = (IPrimitiveType<String>) myContext.getElementDefinition("string").newInstance();
|
||||
name.setValue(theName);
|
||||
parameterElem.getChildByName("name").getMutator().setValue(parameter, name);
|
||||
|
||||
if (theValue instanceof IBaseDatatype) {
|
||||
BaseRuntimeElementDefinition<?> datatypeDef = myContext.getElementDefinition(theValue.getClass());
|
||||
if (datatypeDef instanceof IRuntimeDatatypeDefinition) {
|
||||
Class<? extends IBaseDatatype> profileOf = ((IRuntimeDatatypeDefinition) datatypeDef).getProfileOf();
|
||||
if (profileOf != null) {
|
||||
datatypeDef = myContext.getElementDefinition(profileOf);
|
||||
}
|
||||
}
|
||||
String childElementName = "value" + StringUtils.capitalize(datatypeDef.getName());
|
||||
BaseRuntimeChildDefinition childByName = parameterElem.getChildByName(childElementName);
|
||||
childByName.getMutator().setValue(parameter, theValue);
|
||||
} else if (theValue instanceof IBaseResource) {
|
||||
parameterElem.getChildByName("resource").getMutator().setValue(parameter, theValue);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Don't know how to handle parameter of type " + theValue.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
@Override
|
||||
public IOperationUntypedWithInputAndPartialOutput andParameter(String theName, IBase theValue) {
|
||||
Validate.notEmpty(theName, "theName must not be null");
|
||||
Validate.notNull(theValue, "theValue must not be null");
|
||||
addParam(theName, theValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IOperationUntypedWithInputAndPartialOutput andSearchParameter(String theName, IQueryParameterType theValue) {
|
||||
addParam(theName, theValue);
|
||||
|
||||
public IOperationUntypedWithInput withParameters(IBaseParameters theParameters) {
|
||||
Validate.notNull(theParameters, "theParameters can not be null");
|
||||
myParameters = theParameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -1576,18 +1616,12 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
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> {
|
||||
|
||||
@Override
|
||||
public BaseOperationOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders)
|
||||
throws BaseServerResponseException {
|
||||
public BaseOperationOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException {
|
||||
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
|
||||
if (respType == null) {
|
||||
return null;
|
||||
|
@ -1608,25 +1642,25 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
}
|
||||
|
||||
private final class OutcomeResponseHandler implements IClientResponseHandler<MethodOutcome> {
|
||||
private final String myResourceName;
|
||||
private PreferReturnEnum myPrefer;
|
||||
private final String myResourceName;
|
||||
|
||||
private OutcomeResponseHandler(String theResourceName) {
|
||||
myResourceName = theResourceName;
|
||||
}
|
||||
|
||||
private OutcomeResponseHandler(String theResourceName, PreferReturnEnum thePrefer) {
|
||||
this(theResourceName);
|
||||
myPrefer = thePrefer;
|
||||
}
|
||||
|
||||
private OutcomeResponseHandler(String theResourceName) {
|
||||
myResourceName = theResourceName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException {
|
||||
MethodOutcome response = MethodUtil.process2xxResponse(myContext, myResourceName, theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders);
|
||||
if (theResponseStatusCode == Constants.STATUS_HTTP_201_CREATED) {
|
||||
response.setCreated(true);
|
||||
}
|
||||
|
||||
|
||||
if (myPrefer == PreferReturnEnum.REPRESENTATION) {
|
||||
if (response.getResource() == null) {
|
||||
if (response.getId() != null && isNotBlank(response.getId().getValue()) && response.getId().hasBaseUrl()) {
|
||||
|
@ -1636,7 +1670,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
@ -1779,8 +1813,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public List<IBaseResource> invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders)
|
||||
throws BaseServerResponseException {
|
||||
public List<IBaseResource> invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException {
|
||||
if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) {
|
||||
Class<? extends IBaseResource> bundleType = myContext.getResourceDefinition("Bundle").getImplementingClass();
|
||||
ResourceResponseHandler<IBaseResource> handler = new ResourceResponseHandler<IBaseResource>((Class<IBaseResource>) bundleType);
|
||||
|
@ -1795,7 +1828,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private class SearchInternal extends BaseClientExecutable<IQuery<Object>, Object>implements IQuery<Object>, IUntypedQuery {
|
||||
private class SearchInternal extends BaseClientExecutable<IQuery<Object>, Object> implements IQuery<Object>, IUntypedQuery {
|
||||
|
||||
private String myCompartmentName;
|
||||
private CriterionList myCriterion = new CriterionList();
|
||||
|
@ -1809,10 +1842,10 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
private Class<? extends IBaseBundle> myReturnBundleType;
|
||||
private List<Include> myRevInclude = new ArrayList<Include>();
|
||||
private SearchStyleEnum mySearchStyle;
|
||||
private String mySearchUrl;
|
||||
private List<TokenParam> mySecurity = new ArrayList<TokenParam>();
|
||||
private List<SortInternal> mySort = new ArrayList<SortInternal>();
|
||||
private List<TokenParam> myTags = new ArrayList<TokenParam>();
|
||||
private String mySearchUrl;
|
||||
|
||||
public SearchInternal() {
|
||||
myResourceType = null;
|
||||
|
@ -1826,6 +1859,44 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IQuery byUrl(String theSearchUrl) {
|
||||
Validate.notBlank(theSearchUrl, "theSearchUrl must not be blank/null");
|
||||
|
||||
if (theSearchUrl.startsWith("http://") || theSearchUrl.startsWith("https://")) {
|
||||
mySearchUrl = theSearchUrl;
|
||||
int qIndex = mySearchUrl.indexOf('?');
|
||||
if (qIndex != -1) {
|
||||
mySearchUrl = mySearchUrl.substring(0, qIndex) + validateAndEscapeConditionalUrl(mySearchUrl.substring(qIndex));
|
||||
}
|
||||
} else {
|
||||
String searchUrl = theSearchUrl;
|
||||
if (searchUrl.startsWith("/")) {
|
||||
searchUrl = searchUrl.substring(1);
|
||||
}
|
||||
if (!searchUrl.matches("[a-zA-Z]+($|\\?.*)")) {
|
||||
throw new IllegalArgumentException("Search URL must be either a complete URL starting with http: or https:, or a relative FHIR URL in the form [ResourceType]?[Params]");
|
||||
}
|
||||
int qIndex = searchUrl.indexOf('?');
|
||||
if (qIndex == -1) {
|
||||
mySearchUrl = getUrlBase() + '/' + searchUrl;
|
||||
} else {
|
||||
mySearchUrl = getUrlBase() + '/' + validateAndEscapeConditionalUrl(searchUrl);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IQuery count(int theLimitTo) {
|
||||
if (theLimitTo > 0) {
|
||||
myParamLimit = theLimitTo;
|
||||
} else {
|
||||
myParamLimit = null;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBase execute() {
|
||||
|
||||
|
@ -1861,8 +1932,27 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
addParam(params, Constants.PARAM_REVINCLUDE, next.getValue());
|
||||
}
|
||||
|
||||
for (SortInternal next : mySort) {
|
||||
addParam(params, next.getParamName(), next.getParamValue());
|
||||
if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2)) {
|
||||
SortSpec rootSs = null;
|
||||
SortSpec lastSs = null;
|
||||
for (SortInternal next : mySort) {
|
||||
SortSpec nextSortSpec = new SortSpec();
|
||||
nextSortSpec.setParamName(next.getParamValue());
|
||||
nextSortSpec.setOrder(next.getDirection());
|
||||
if (rootSs == null) {
|
||||
rootSs = nextSortSpec;
|
||||
} else {
|
||||
lastSs.setChain(nextSortSpec);
|
||||
}
|
||||
lastSs = nextSortSpec;
|
||||
}
|
||||
if (rootSs != null) {
|
||||
addParam(params, Constants.PARAM_SORT, SortParameter.createSortStringDstu3(rootSs));
|
||||
}
|
||||
} else {
|
||||
for (SortInternal next : mySort) {
|
||||
addParam(params, next.getParamName(), next.getParamValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (myParamLimit != null) {
|
||||
|
@ -1876,8 +1966,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
}
|
||||
|
||||
if (myReturnBundleType == null && myContext.getVersion().getVersion().isRi()) {
|
||||
throw new IllegalArgumentException("When using the client with HL7.org structures, you must specify "
|
||||
+ "the bundle return type for the client by adding \".returnBundle(org.hl7.fhir.instance.model.Bundle.class)\" to your search method call before the \".execute()\" method");
|
||||
throw new IllegalArgumentException("When using the client with HL7.org structures, you must specify " + "the bundle return type for the client by adding \".returnBundle(org.hl7.fhir.instance.model.Bundle.class)\" to your search method call before the \".execute()\" method");
|
||||
}
|
||||
|
||||
IClientResponseHandler<? extends IBase> binding;
|
||||
|
@ -1934,16 +2023,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
return count(theLimitTo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IQuery count(int theLimitTo) {
|
||||
if (theLimitTo > 0) {
|
||||
myParamLimit = theLimitTo;
|
||||
} else {
|
||||
myParamLimit = null;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IQuery returnBundle(Class theClass) {
|
||||
if (theClass == null) {
|
||||
|
@ -2017,34 +2096,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IQuery byUrl(String theSearchUrl) {
|
||||
Validate.notBlank(theSearchUrl, "theSearchUrl must not be blank/null");
|
||||
|
||||
if (theSearchUrl.startsWith("http://") || theSearchUrl.startsWith("https://")) {
|
||||
mySearchUrl = theSearchUrl;
|
||||
int qIndex = mySearchUrl.indexOf('?');
|
||||
if (qIndex != -1) {
|
||||
mySearchUrl = mySearchUrl.substring(0, qIndex) + validateAndEscapeConditionalUrl(mySearchUrl.substring(qIndex));
|
||||
}
|
||||
} else {
|
||||
String searchUrl = theSearchUrl;
|
||||
if (searchUrl.startsWith("/")) {
|
||||
searchUrl = searchUrl.substring(1);
|
||||
}
|
||||
if (!searchUrl.matches("[a-zA-Z]+($|\\?.*)")) {
|
||||
throw new IllegalArgumentException("Search URL must be either a complete URL starting with http: or https:, or a relative FHIR URL in the form [ResourceType]?[Params]");
|
||||
}
|
||||
int qIndex = searchUrl.indexOf('?');
|
||||
if (qIndex == -1) {
|
||||
mySearchUrl = getUrlBase() + '/' + searchUrl;
|
||||
} else {
|
||||
mySearchUrl = getUrlBase() + '/' + validateAndEscapeConditionalUrl(searchUrl);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
|
@ -2053,6 +2104,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
private SearchInternal myFor;
|
||||
private String myParamName;
|
||||
private String myParamValue;
|
||||
private SortOrderEnum myDirection;
|
||||
|
||||
public SortInternal(SearchInternal theFor) {
|
||||
myFor = theFor;
|
||||
|
@ -2061,13 +2113,23 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
@Override
|
||||
public IQuery ascending(IParam theParam) {
|
||||
myParamName = Constants.PARAM_SORT_ASC;
|
||||
myDirection = SortOrderEnum.ASC;
|
||||
myParamValue = theParam.getParamName();
|
||||
return myFor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IQuery ascending(String theParam) {
|
||||
myParamName = Constants.PARAM_SORT_ASC;
|
||||
myDirection = SortOrderEnum.ASC;
|
||||
myParamValue = theParam;
|
||||
return myFor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IQuery defaultOrder(IParam theParam) {
|
||||
myParamName = Constants.PARAM_SORT;
|
||||
myDirection = null;
|
||||
myParamValue = theParam.getParamName();
|
||||
return myFor;
|
||||
}
|
||||
|
@ -2075,10 +2137,23 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
@Override
|
||||
public IQuery descending(IParam theParam) {
|
||||
myParamName = Constants.PARAM_SORT_DESC;
|
||||
myDirection = SortOrderEnum.DESC;
|
||||
myParamValue = theParam.getParamName();
|
||||
return myFor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IQuery descending(String theParam) {
|
||||
myParamName = Constants.PARAM_SORT_DESC;
|
||||
myDirection = SortOrderEnum.DESC;
|
||||
myParamValue = theParam;
|
||||
return myFor;
|
||||
}
|
||||
|
||||
public SortOrderEnum getDirection() {
|
||||
return myDirection;
|
||||
}
|
||||
|
||||
public String getParamName() {
|
||||
return myParamName;
|
||||
}
|
||||
|
@ -2092,8 +2167,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
private final class StringResponseHandler implements IClientResponseHandler<String> {
|
||||
|
||||
@Override
|
||||
public String invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders)
|
||||
throws IOException, BaseServerResponseException {
|
||||
public String invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
|
||||
return IOUtils.toString(theResponseReader);
|
||||
}
|
||||
}
|
||||
|
@ -2111,7 +2185,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
}
|
||||
}
|
||||
|
||||
private final class TransactionExecutable<T> extends BaseClientExecutable<ITransactionTyped<T>, T>implements ITransactionTyped<T> {
|
||||
private final class TransactionExecutable<T> extends BaseClientExecutable<ITransactionTyped<T>, T> implements ITransactionTyped<T> {
|
||||
|
||||
private IBaseBundle myBaseBundle;
|
||||
private Bundle myBundle;
|
||||
|
@ -2201,7 +2275,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
|
||||
}
|
||||
|
||||
private class UpdateInternal extends BaseClientExecutable<IUpdateExecutable, MethodOutcome>implements IUpdate, IUpdateTyped, IUpdateExecutable, IUpdateWithQuery, IUpdateWithQueryTyped {
|
||||
private class UpdateInternal extends BaseClientExecutable<IUpdateExecutable, MethodOutcome> implements IUpdate, IUpdateTyped, IUpdateExecutable, IUpdateWithQuery, IUpdateWithQueryTyped {
|
||||
|
||||
private CriterionList myCriterionList;
|
||||
private IIdType myId;
|
||||
|
@ -2319,7 +2393,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
|
||||
}
|
||||
|
||||
private class ValidateInternal extends BaseClientExecutable<IValidateUntyped, MethodOutcome>implements IValidate, IValidateUntyped {
|
||||
private class ValidateInternal extends BaseClientExecutable<IValidateUntyped, MethodOutcome> implements IValidate, IValidateUntyped {
|
||||
private IBaseResource myResource;
|
||||
|
||||
@Override
|
||||
|
@ -2361,34 +2435,4 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
|
||||
}
|
||||
|
||||
private static String validateAndEscapeConditionalUrl(String theSearchUrl) {
|
||||
Validate.notBlank(theSearchUrl, "Conditional URL can not be blank/null");
|
||||
StringBuilder b = new StringBuilder();
|
||||
boolean haveHadQuestionMark = false;
|
||||
for (int i = 0; i < theSearchUrl.length(); i++) {
|
||||
char nextChar = theSearchUrl.charAt(i);
|
||||
if (!haveHadQuestionMark) {
|
||||
if (nextChar == '?') {
|
||||
haveHadQuestionMark = true;
|
||||
} else if (!Character.isLetter(nextChar)) {
|
||||
throw new IllegalArgumentException("Conditional URL must be in the format \"[ResourceType]?[Params]\" and must not have a base URL - Found: " + theSearchUrl);
|
||||
}
|
||||
b.append(nextChar);
|
||||
} else {
|
||||
switch (nextChar) {
|
||||
case '|':
|
||||
case '?':
|
||||
case '$':
|
||||
case ':':
|
||||
b.append(UrlUtil.escape(Character.toString(nextChar)));
|
||||
break;
|
||||
default:
|
||||
b.append(nextChar);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,10 +22,39 @@ package ca.uhn.fhir.rest.gclient;
|
|||
|
||||
public interface ISort<T> {
|
||||
|
||||
/**
|
||||
* Sort ascending
|
||||
*/
|
||||
IQuery<T> ascending(IParam theParam);
|
||||
|
||||
/**
|
||||
* Sort ascending
|
||||
*
|
||||
* @param theParam The param name, e.g. "address"
|
||||
*/
|
||||
IQuery<T> ascending(String theParam);
|
||||
|
||||
/**
|
||||
* Sort by the default order. Note that as of STU3, there is no longer
|
||||
* a concept of default order, only ascending and descending. This method
|
||||
* technically implies "ascending" but it makes more sense to use
|
||||
* {@link #ascending(IParam)}
|
||||
*/
|
||||
IQuery<T> defaultOrder(IParam theParam);
|
||||
|
||||
|
||||
/**
|
||||
* Sort descending
|
||||
*
|
||||
* @param A query param - Could be a constant such as <code>Patient.ADDRESS</code> or a custom
|
||||
* param such as <code>new StringClientParam("foo")</code>
|
||||
*/
|
||||
IQuery<T> descending(IParam theParam);
|
||||
|
||||
/**
|
||||
* Sort ascending
|
||||
*
|
||||
* @param theParam The param name, e.g. "address"
|
||||
*/
|
||||
IQuery<T> descending(String theParam);
|
||||
|
||||
}
|
||||
|
|
|
@ -483,7 +483,7 @@ public class MethodUtil {
|
|||
} else if (nextAnnotation instanceof Count) {
|
||||
param = new CountParameter();
|
||||
} else if (nextAnnotation instanceof Sort) {
|
||||
param = new SortParameter();
|
||||
param = new SortParameter(theContext);
|
||||
} else if (nextAnnotation instanceof TransactionParam) {
|
||||
param = new TransactionParameter(theContext);
|
||||
} else if (nextAnnotation instanceof ConditionalUrlParam) {
|
||||
|
|
|
@ -26,40 +26,74 @@ import java.util.ArrayList;
|
|||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.rest.annotation.Sort;
|
||||
import ca.uhn.fhir.rest.api.SortOrderEnum;
|
||||
import ca.uhn.fhir.rest.api.SortSpec;
|
||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
public class SortParameter implements IParameter {
|
||||
|
||||
private FhirContext myContext;
|
||||
|
||||
public SortParameter(FhirContext theContext) {
|
||||
myContext = theContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
|
||||
if (theOuterCollectionType != null || theInnerCollectionType != null) {
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + theMethod.getDeclaringClass().getCanonicalName() + "' is annotated with @" + Sort.class.getName() + " but can not be of collection type");
|
||||
}
|
||||
if (!theParameterType.equals(SortSpec.class)) {
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + theMethod.getDeclaringClass().getCanonicalName() + "' is annotated with @" + Sort.class.getName() + " but is an invalid type, must be: " + SortSpec.class.getCanonicalName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException {
|
||||
SortSpec ss = (SortSpec) theSourceClientArgument;
|
||||
while (ss != null) {
|
||||
String name;
|
||||
if (ss.getOrder() == null) {
|
||||
name = Constants.PARAM_SORT;
|
||||
} else if (ss.getOrder() == SortOrderEnum.ASC) {
|
||||
name = Constants.PARAM_SORT_ASC;
|
||||
} else {
|
||||
name = Constants.PARAM_SORT_DESC;
|
||||
|
||||
if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2)) {
|
||||
String string = createSortStringDstu3(ss);
|
||||
if (string.length() > 0) {
|
||||
if (!theTargetQueryArguments.containsKey(Constants.PARAM_SORT)) {
|
||||
theTargetQueryArguments.put(Constants.PARAM_SORT, new ArrayList<String>());
|
||||
}
|
||||
theTargetQueryArguments.get(Constants.PARAM_SORT).add(string);
|
||||
}
|
||||
|
||||
if (ss.getParamName() != null) {
|
||||
if (!theTargetQueryArguments.containsKey(name)) {
|
||||
theTargetQueryArguments.put(name, new ArrayList<String>());
|
||||
} else {
|
||||
|
||||
while (ss != null) {
|
||||
String name;
|
||||
if (ss.getOrder() == null) {
|
||||
name = Constants.PARAM_SORT;
|
||||
} else if (ss.getOrder() == SortOrderEnum.ASC) {
|
||||
name = Constants.PARAM_SORT_ASC;
|
||||
} else {
|
||||
name = Constants.PARAM_SORT_DESC;
|
||||
}
|
||||
theTargetQueryArguments.get(name).add(ss.getParamName());
|
||||
|
||||
if (ss.getParamName() != null) {
|
||||
if (!theTargetQueryArguments.containsKey(name)) {
|
||||
theTargetQueryArguments.put(name, new ArrayList<String>());
|
||||
}
|
||||
|
||||
theTargetQueryArguments.get(name).add(ss.getParamName());
|
||||
}
|
||||
ss = ss.getChain();
|
||||
}
|
||||
ss = ss.getChain();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,17 +123,47 @@ public class SortParameter implements IParameter {
|
|||
|
||||
String[] values = theRequest.getParameters().get(nextParamName);
|
||||
if (values != null) {
|
||||
|
||||
for (String nextValue : values) {
|
||||
if (isNotBlank(nextValue)) {
|
||||
SortSpec spec = new SortSpec();
|
||||
spec.setOrder(order);
|
||||
spec.setParamName(nextValue);
|
||||
if (innerSpec == null) {
|
||||
outerSpec = spec;
|
||||
innerSpec = spec;
|
||||
} else {
|
||||
innerSpec.setChain(spec);
|
||||
innerSpec = spec;
|
||||
|
||||
if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2) && order == null) {
|
||||
StringTokenizer tok = new StringTokenizer(nextValue, ",");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String next = tok.nextToken();
|
||||
if (isNotBlank(next) && !next.equals("-")) {
|
||||
order = SortOrderEnum.ASC;
|
||||
if (next.startsWith("-")) {
|
||||
order = SortOrderEnum.DESC;
|
||||
next = next.substring(1);
|
||||
}
|
||||
|
||||
SortSpec spec = new SortSpec();
|
||||
spec.setOrder(order);
|
||||
spec.setParamName(next);
|
||||
if (innerSpec == null) {
|
||||
outerSpec = spec;
|
||||
innerSpec = spec;
|
||||
} else {
|
||||
innerSpec.setChain(spec);
|
||||
innerSpec = spec;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (isNotBlank(nextValue)) {
|
||||
SortSpec spec = new SortSpec();
|
||||
spec.setOrder(order);
|
||||
spec.setParamName(nextValue);
|
||||
if (innerSpec == null) {
|
||||
outerSpec = spec;
|
||||
innerSpec = spec;
|
||||
} else {
|
||||
innerSpec.setChain(spec);
|
||||
innerSpec = spec;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -109,15 +173,25 @@ public class SortParameter implements IParameter {
|
|||
return outerSpec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
|
||||
if (theOuterCollectionType != null || theInnerCollectionType != null) {
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + theMethod.getDeclaringClass().getCanonicalName() + "' is annotated with @" + Sort.class.getName() + " but can not be of collection type");
|
||||
}
|
||||
if (!theParameterType.equals(SortSpec.class)) {
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + theMethod.getDeclaringClass().getCanonicalName() + "' is annotated with @" + Sort.class.getName() + " but is an invalid type, must be: " + SortSpec.class.getCanonicalName());
|
||||
public static String createSortStringDstu3(SortSpec ss) {
|
||||
StringBuilder val = new StringBuilder();
|
||||
while (ss != null) {
|
||||
|
||||
if (isNotBlank(ss.getParamName())) {
|
||||
if (val.length() > 0) {
|
||||
val.append(',');
|
||||
}
|
||||
if (ss.getOrder() == SortOrderEnum.DESC) {
|
||||
val.append('-');
|
||||
}
|
||||
val.append(ParameterUtil.escape(ss.getParamName()));
|
||||
}
|
||||
|
||||
ss = ss.getChain();
|
||||
}
|
||||
|
||||
String string = val.toString();
|
||||
return string;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.apache.http.message.BasicHeader;
|
|||
import org.apache.http.message.BasicStatusLine;
|
||||
import org.hl7.fhir.dstu3.model.Binary;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.Bundle.BundleType;
|
||||
import org.hl7.fhir.dstu3.model.Conformance;
|
||||
import org.hl7.fhir.dstu3.model.OperationOutcome;
|
||||
import org.hl7.fhir.dstu3.model.Parameters;
|
||||
|
@ -607,6 +608,60 @@ public class GenericClientDstu3Test {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* See #371
|
||||
*/
|
||||
@Test
|
||||
public void testSortDstu3Test() throws Exception {
|
||||
IParser p = ourCtx.newXmlParser();
|
||||
|
||||
Bundle b = new Bundle();
|
||||
b.setType(BundleType.SEARCHSET);
|
||||
|
||||
final String respString = p.encodeResourceToString(b);
|
||||
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
|
||||
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
|
||||
@Override
|
||||
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
|
||||
}
|
||||
});
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
int idx = 0;
|
||||
|
||||
//@formatter:off
|
||||
client
|
||||
.search()
|
||||
.forResource(Patient.class)
|
||||
.sort().ascending("address")
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
assertEquals("http://example.com/fhir/Patient?_sort=address", capt.getAllValues().get(idx++).getURI().toASCIIString());
|
||||
|
||||
client
|
||||
.search()
|
||||
.forResource(Patient.class)
|
||||
.sort().descending("address")
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
assertEquals("http://example.com/fhir/Patient?_sort=-address", capt.getAllValues().get(idx++).getURI().toASCIIString());
|
||||
|
||||
client
|
||||
.search()
|
||||
.forResource(Patient.class)
|
||||
.sort().descending("address")
|
||||
.sort().ascending("name")
|
||||
.sort().descending(Patient.BIRTHDATE)
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
assertEquals("http://example.com/fhir/Patient?_sort=-address%2Cname%2C-birthdate", capt.getAllValues().get(idx++).getURI().toASCIIString());
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserAgentForConformance() throws Exception {
|
||||
IParser p = ourCtx.newXmlParser();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package ca.uhn.fhir.rest.client;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
@ -33,6 +33,9 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
import ca.uhn.fhir.rest.annotation.Count;
|
||||
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.annotation.Sort;
|
||||
import ca.uhn.fhir.rest.api.SortOrderEnum;
|
||||
import ca.uhn.fhir.rest.api.SortSpec;
|
||||
import ca.uhn.fhir.rest.client.api.IRestfulClient;
|
||||
import ca.uhn.fhir.rest.param.StringParam;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
|
@ -91,6 +94,35 @@ public class SearchClientDstu3Test {
|
|||
assertEquals("http://localhost:8081/hapi-fhir/fhir/Location?_query=match&name=smith&_count=100", value.getURI().toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* See #371
|
||||
*/
|
||||
@Test
|
||||
public void testSortForDstu3() throws Exception {
|
||||
|
||||
final String response = createBundleWithSearchExtension();
|
||||
ArgumentCaptor<HttpUriRequest> 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(response), Charset.forName("UTF-8"));
|
||||
}
|
||||
});
|
||||
|
||||
ILocationClient client = ourCtx.newRestfulClient(ILocationClient.class, "http://localhost/fhir");
|
||||
|
||||
int idx = 0;
|
||||
|
||||
client.search(new SortSpec("param1", SortOrderEnum.ASC));
|
||||
assertEquals("http://localhost/fhir/Bundle?_sort=param1", ((HttpGet) capt.getAllValues().get(idx++)).getURI().toString());
|
||||
|
||||
client.search(new SortSpec("param1", SortOrderEnum.ASC).setChain(new SortSpec("param2", SortOrderEnum.DESC)));
|
||||
assertEquals("http://localhost/fhir/Bundle?_sort=param1%2C-param2", ((HttpGet) capt.getAllValues().get(idx++)).getURI().toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* See #299
|
||||
*/
|
||||
|
@ -159,6 +191,9 @@ public class SearchClientDstu3Test {
|
|||
|
||||
@Search(queryName = "match", type=Location.class)
|
||||
public Bundle getMatchesReturnBundle(final @RequiredParam(name = Location.SP_NAME) StringParam name, final @Count Integer count);
|
||||
|
||||
@Search
|
||||
public Bundle search(@Sort SortSpec theSort);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.hl7.fhir.dstu3.model.HumanName;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.annotation.Count;
|
||||
import ca.uhn.fhir.rest.annotation.OptionalParam;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.annotation.Sort;
|
||||
import ca.uhn.fhir.rest.api.SortOrderEnum;
|
||||
import ca.uhn.fhir.rest.api.SortSpec;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
|
||||
public class SearchSortDstu3Test {
|
||||
|
||||
private static CloseableHttpClient ourClient;
|
||||
private static FhirContext ourCtx = FhirContext.forDstu3();
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchSortDstu3Test.class);
|
||||
private static int ourPort;
|
||||
private static Server ourServer;
|
||||
private static String ourLastMethod;
|
||||
private static SortSpec ourLastSortSpec;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
ourLastMethod = null;
|
||||
ourLastSortSpec = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearch() throws Exception {
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_sort=param1,-param2,param3,-param4");
|
||||
CloseableHttpResponse status = ourClient.execute(httpGet);
|
||||
try {
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
ourLog.info(responseContent);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertEquals("search", ourLastMethod);
|
||||
|
||||
assertEquals("param1", ourLastSortSpec.getParamName());
|
||||
assertEquals(SortOrderEnum.ASC, ourLastSortSpec.getOrder());
|
||||
|
||||
assertEquals("param2", ourLastSortSpec.getChain().getParamName());
|
||||
assertEquals(SortOrderEnum.DESC, ourLastSortSpec.getChain().getOrder());
|
||||
|
||||
assertEquals("param3", ourLastSortSpec.getChain().getChain().getParamName());
|
||||
assertEquals(SortOrderEnum.ASC, ourLastSortSpec.getChain().getChain().getOrder());
|
||||
|
||||
assertEquals("param4", ourLastSortSpec.getChain().getChain().getChain().getParamName());
|
||||
assertEquals(SortOrderEnum.DESC, ourLastSortSpec.getChain().getChain().getChain().getOrder());
|
||||
|
||||
} finally {
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
ourServer.stop();
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
ourPort = PortUtil.findFreePort();
|
||||
ourServer = new Server(ourPort);
|
||||
|
||||
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
RestfulServer servlet = new RestfulServer(ourCtx);
|
||||
servlet.setPagingProvider(new FifoMemoryPagingProvider(10));
|
||||
|
||||
servlet.setResourceProviders(patientProvider);
|
||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||
ourServer.setHandler(proxyHandler);
|
||||
ourServer.start();
|
||||
|
||||
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||
builder.setConnectionManager(connectionManager);
|
||||
ourClient = builder.build();
|
||||
|
||||
}
|
||||
|
||||
public static class DummyPatientResourceProvider implements IResourceProvider {
|
||||
|
||||
@Override
|
||||
public Class<? extends IBaseResource> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
|
||||
//@formatter:off
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Search()
|
||||
public List search(
|
||||
@Sort SortSpec theSortSpec
|
||||
) {
|
||||
ourLastMethod = "search";
|
||||
ourLastSortSpec = theSortSpec;
|
||||
ArrayList<Patient> retVal = new ArrayList<Patient>();
|
||||
for (int i = 1; i < 100; i++) {
|
||||
retVal.add((Patient) new Patient().addName(new HumanName().addFamily("FAMILY")).setId("" + i));
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
//@formatter:on
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -259,6 +259,10 @@
|
|||
the spec says they should. Thanks to Jim Steel for
|
||||
reporting!
|
||||
</action>
|
||||
<action type="fix" issue="371">
|
||||
Update STU3 client and server to use the new sort parameter style (param1,-param2,param). Thanks to GitHub user @euz1e4r for
|
||||
reporting!
|
||||
</action>
|
||||
</release>
|
||||
<release version="1.5" date="2016-04-20">
|
||||
<action type="fix" issue="339">
|
||||
|
|
Loading…
Reference in New Issue