Improve search method binding priority (#1802)
* Work on search method binding priority * Work on method priority * Work on binding priority * Test fixes * Add changelog * Test fixes * compile fix * One more comple fix * Test cleanup * Test fix
This commit is contained in:
parent
8873749d9c
commit
497757501b
|
@ -118,6 +118,19 @@ public class ParameterUtil {
|
|||
return parseQueryParams(theContext, paramType, theUnqualifiedParamName, theParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes :modifiers and .chains from URL parameter names
|
||||
*/
|
||||
public static String stripModifierPart(String theParam) {
|
||||
for (int i = 0; i < theParam.length(); i++) {
|
||||
char nextChar = theParam.charAt(i);
|
||||
if (nextChar == ':' || nextChar == '.') {
|
||||
return theParam.substring(0, i);
|
||||
}
|
||||
}
|
||||
return theParam;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a string according to the rules for parameter escaping specified in the <a href="http://www.hl7.org/implement/standards/fhir/search.html#escaping">FHIR Specification Escaping
|
||||
* Section</a>
|
||||
|
|
|
@ -31,31 +31,63 @@ import java.lang.reflect.ParameterizedType;
|
|||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.lang.reflect.WildcardType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class ReflectionUtil {
|
||||
|
||||
private static final ConcurrentHashMap<String, Object> ourFhirServerVersions = new ConcurrentHashMap<>();
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ReflectionUtil.class);
|
||||
public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
|
||||
public static final Class<?>[] EMPTY_CLASS_ARRAY = new Class[0];
|
||||
private static final ConcurrentHashMap<String, Object> ourFhirServerVersions = new ConcurrentHashMap<>();
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ReflectionUtil.class);
|
||||
|
||||
public static LinkedHashSet<Method> getDeclaredMethods(Class<?> theClazz) {
|
||||
LinkedHashSet<Method> retVal = new LinkedHashSet<>();
|
||||
for (Method next : theClazz.getDeclaredMethods()) {
|
||||
/**
|
||||
* Returns all methods declared against {@literal theClazz}. This method returns a predictable order, which is
|
||||
* sorted by method name and then by parameters.
|
||||
*/
|
||||
public static List<Method> getDeclaredMethods(Class<?> theClazz) {
|
||||
HashSet<Method> foundMethods = new LinkedHashSet<>();
|
||||
Method[] declaredMethods = theClazz.getDeclaredMethods();
|
||||
for (Method next : declaredMethods) {
|
||||
try {
|
||||
Method method = theClazz.getMethod(next.getName(), next.getParameterTypes());
|
||||
retVal.add(method);
|
||||
foundMethods.add(method);
|
||||
} catch (NoSuchMethodException | SecurityException e) {
|
||||
retVal.add(next);
|
||||
foundMethods.add(next);
|
||||
}
|
||||
}
|
||||
|
||||
List<Method> retVal = new ArrayList<>(foundMethods);
|
||||
retVal.sort((Comparator.comparing(ReflectionUtil::describeMethodInSortFriendlyWay)));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a description like <code>startsWith params(java.lang.String, int) returns(boolean)</code>.
|
||||
* The format is chosen in order to provide a predictable and useful sorting order.
|
||||
*/
|
||||
public static String describeMethodInSortFriendlyWay(Method theMethod) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append(theMethod.getName());
|
||||
b.append(" returns(");
|
||||
b.append(theMethod.getReturnType().getName());
|
||||
b.append(") params(");
|
||||
Class<?>[] parameterTypes = theMethod.getParameterTypes();
|
||||
for (int i = 0, parameterTypesLength = parameterTypes.length; i < parameterTypesLength; i++) {
|
||||
if (i > 0) {
|
||||
b.append(", ");
|
||||
}
|
||||
Class<?> next = parameterTypes[i];
|
||||
b.append(next.getName());
|
||||
}
|
||||
b.append(")");
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
public static Class<?> getGenericCollectionTypeOfField(Field next) {
|
||||
ParameterizedType collectionType = (ParameterizedType) next.getGenericType();
|
||||
return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]);
|
||||
|
|
|
@ -2,6 +2,7 @@ package ca.uhn.fhir.util;
|
|||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -51,4 +52,12 @@ public class ReflectionUtilTest {
|
|||
assertFalse(ReflectionUtil.typeExists("ca.Foo"));
|
||||
assertTrue(ReflectionUtil.typeExists(String.class.getName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDescribeMethod() throws NoSuchMethodException {
|
||||
Method method = String.class.getMethod("startsWith", String.class, int.class);
|
||||
String description = ReflectionUtil.describeMethodInSortFriendlyWay(method);
|
||||
assertEquals("startsWith returns(boolean) params(java.lang.String, int)", description);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
type: add
|
||||
issue: 1802
|
||||
title: "In a plain server, if a Resource Provider class had two methods with the same parameter names
|
||||
(as specified in the @OptionalParam or @RequiredParam) but different cardinalities, the server could
|
||||
sometimes pick the incorrect method to execute. The selection algorithm has been improved to no longer
|
||||
have this issue, and to be more consistent and predictable in terms of which resource provider
|
||||
method is selected when the choice is somewhat ambiguous."
|
|
@ -224,7 +224,7 @@ public abstract class BaseStorageDao {
|
|||
|
||||
Set<String> paramNames = theSource.keySet();
|
||||
for (String nextParamName : paramNames) {
|
||||
QualifierDetails qualifiedParamName = SearchMethodBinding.extractQualifiersFromParameterName(nextParamName);
|
||||
QualifierDetails qualifiedParamName = QualifierDetails.extractQualifiersFromParameterName(nextParamName);
|
||||
RuntimeSearchParam param = searchParams.get(qualifiedParamName.getParamName());
|
||||
if (param == null) {
|
||||
String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "invalidSearchParameter", qualifiedParamName.getParamName(), new TreeSet<>(searchParams.keySet()));
|
||||
|
|
|
@ -29,10 +29,18 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.in;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.nullable;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
public class FhirResourceDaoR4InterceptorTest extends BaseJpaR4Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4InterceptorTest.class);
|
||||
|
|
|
@ -25,9 +25,10 @@ import java.util.List;
|
|||
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
|
||||
import ca.uhn.fhir.rest.server.method.MethodMatchEnum;
|
||||
|
||||
/**
|
||||
* Created by dsotnikov on 2/25/2014.
|
||||
* Holds all method bindings for an individual resource type
|
||||
*/
|
||||
public class ResourceBinding {
|
||||
|
||||
|
@ -36,9 +37,16 @@ public class ResourceBinding {
|
|||
private String resourceName;
|
||||
private List<BaseMethodBinding<?>> myMethodBindings = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public ResourceBinding() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public ResourceBinding(String resourceName, List<BaseMethodBinding<?>> methods) {
|
||||
this.resourceName = resourceName;
|
||||
this.myMethodBindings = methods;
|
||||
|
@ -51,14 +59,28 @@ public class ResourceBinding {
|
|||
}
|
||||
|
||||
ourLog.debug("Looking for a handler for {}", theRequest);
|
||||
|
||||
/*
|
||||
* Look for the method with the highest match strength
|
||||
*/
|
||||
|
||||
BaseMethodBinding<?> matchedMethod = null;
|
||||
MethodMatchEnum matchedMethodStrength = null;
|
||||
|
||||
for (BaseMethodBinding<?> rm : myMethodBindings) {
|
||||
if (rm.incomingServerRequestMatchesMethod(theRequest)) {
|
||||
ourLog.debug("Handler {} matches", rm);
|
||||
return rm;
|
||||
MethodMatchEnum nextMethodMatch = rm.incomingServerRequestMatchesMethod(theRequest);
|
||||
if (nextMethodMatch != MethodMatchEnum.NONE) {
|
||||
if (matchedMethodStrength == null || matchedMethodStrength.ordinal() < nextMethodMatch.ordinal()) {
|
||||
matchedMethod = rm;
|
||||
matchedMethodStrength = nextMethodMatch;
|
||||
}
|
||||
if (matchedMethodStrength == MethodMatchEnum.EXACT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ourLog.trace("Handler {} does not match", rm);
|
||||
}
|
||||
return null;
|
||||
|
||||
return matchedMethod;
|
||||
}
|
||||
|
||||
public String getResourceName() {
|
||||
|
|
|
@ -45,6 +45,7 @@ import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor;
|
|||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
|
||||
import ca.uhn.fhir.rest.server.method.ConformanceMethodBinding;
|
||||
import ca.uhn.fhir.rest.server.method.MethodMatchEnum;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.tenant.ITenantIdentificationStrategy;
|
||||
import ca.uhn.fhir.util.*;
|
||||
|
@ -298,7 +299,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
|||
ResourceBinding resourceBinding = null;
|
||||
BaseMethodBinding<?> resourceMethod = null;
|
||||
String resourceName = requestDetails.getResourceName();
|
||||
if (myServerConformanceMethod.incomingServerRequestMatchesMethod(requestDetails)) {
|
||||
if (myServerConformanceMethod.incomingServerRequestMatchesMethod(requestDetails) != MethodMatchEnum.NONE) {
|
||||
resourceMethod = myServerConformanceMethod;
|
||||
} else if (resourceName == null) {
|
||||
resourceBinding = myServerBinding;
|
||||
|
@ -1785,6 +1786,21 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters all plain and resource providers (but not the conformance provider).
|
||||
*/
|
||||
public void unregisterAllProviders() {
|
||||
unregisterAllProviders(myPlainProviders);
|
||||
unregisterAllProviders(myResourceProviders);
|
||||
}
|
||||
|
||||
private void unregisterAllProviders(List<?> theProviders) {
|
||||
while (theProviders.size() > 0) {
|
||||
unregisterProvider(theProviders.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void writeExceptionToResponse(HttpServletResponse theResponse, BaseServerResponseException theException) throws IOException {
|
||||
theResponse.setStatus(theException.getStatusCode());
|
||||
addHeadersToResponse(theResponse);
|
||||
|
|
|
@ -26,47 +26,44 @@ import ca.uhn.fhir.interceptor.api.HookParams;
|
|||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.parser.IParser;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.api.server.IRestfulServer;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
|
||||
import ca.uhn.fhir.rest.server.BundleProviders;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.util.ReflectionUtil;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public abstract class BaseMethodBinding<T> {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseMethodBinding.class);
|
||||
private final List<BaseQueryParameter> myQueryParameters;
|
||||
private FhirContext myContext;
|
||||
private Method myMethod;
|
||||
private List<IParameter> myParameters;
|
||||
private Object myProvider;
|
||||
private boolean mySupportsConditional;
|
||||
private boolean mySupportsConditionalMultiple;
|
||||
|
||||
public BaseMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
|
||||
assert theMethod != null;
|
||||
assert theContext != null;
|
||||
|
@ -75,6 +72,11 @@ public abstract class BaseMethodBinding<T> {
|
|||
myContext = theContext;
|
||||
myProvider = theProvider;
|
||||
myParameters = MethodUtil.getResourceParameters(theContext, theMethod, theProvider, getRestOperationType());
|
||||
myQueryParameters = myParameters
|
||||
.stream()
|
||||
.filter(t -> t instanceof BaseQueryParameter)
|
||||
.map(t -> (BaseQueryParameter) t)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (IParameter next : myParameters) {
|
||||
if (next instanceof ConditionalParamBinder) {
|
||||
|
@ -90,6 +92,10 @@ public abstract class BaseMethodBinding<T> {
|
|||
myMethod.setAccessible(true);
|
||||
}
|
||||
|
||||
protected List<BaseQueryParameter> getQueryParameters() {
|
||||
return myQueryParameters;
|
||||
}
|
||||
|
||||
protected Object[] createMethodParams(RequestDetails theRequest) {
|
||||
Object[] params = new Object[getParameters().size()];
|
||||
for (int i = 0; i < getParameters().size(); i++) {
|
||||
|
@ -211,7 +217,7 @@ public abstract class BaseMethodBinding<T> {
|
|||
return getRestOperationType();
|
||||
}
|
||||
|
||||
public abstract boolean incomingServerRequestMatchesMethod(RequestDetails theRequest);
|
||||
public abstract MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest);
|
||||
|
||||
public abstract Object invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException;
|
||||
|
||||
|
|
|
@ -111,20 +111,20 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
Set<RequestTypeEnum> allowableRequestTypes = provideAllowableRequestTypes();
|
||||
RequestTypeEnum requestType = theRequest.getRequestType();
|
||||
if (!allowableRequestTypes.contains(requestType)) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
if (!getResourceName().equals(theRequest.getResourceName())) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
if (getMatchingOperation() == null && StringUtils.isNotBlank(theRequest.getOperation())) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
if (getMatchingOperation() != null && !getMatchingOperation().equals(theRequest.getOperation())) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -137,7 +137,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
|
|||
* It's also needed for conditional update..
|
||||
*/
|
||||
|
||||
return true;
|
||||
return MethodMatchEnum.EXACT;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -58,6 +58,8 @@ public abstract class BaseQueryParameter implements IParameter {
|
|||
return null;
|
||||
}
|
||||
|
||||
protected abstract boolean supportsRepetition();
|
||||
|
||||
/**
|
||||
* Parameter should return true if {@link #parse(FhirContext, List)} should be called even if the query string
|
||||
* contained no values for the given parameter
|
||||
|
|
|
@ -152,25 +152,25 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
if (theRequest.getRequestType() == RequestTypeEnum.OPTIONS) {
|
||||
if (theRequest.getOperation() == null && theRequest.getResourceName() == null) {
|
||||
return true;
|
||||
return MethodMatchEnum.EXACT;
|
||||
}
|
||||
}
|
||||
|
||||
if (theRequest.getResourceName() != null) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
|
||||
if ("metadata".equals(theRequest.getOperation())) {
|
||||
if (theRequest.getRequestType() == RequestTypeEnum.GET) {
|
||||
return true;
|
||||
return MethodMatchEnum.EXACT;
|
||||
}
|
||||
throw new MethodNotAllowedException("/metadata request must use HTTP GET", RequestTypeEnum.GET);
|
||||
}
|
||||
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
|
|
@ -29,10 +29,8 @@ import ca.uhn.fhir.rest.api.server.IRestfulServer;
|
|||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.ResponseDetails;
|
||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import org.hl7.fhir.instance.model.api.IBaseCoding;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
@ -69,12 +67,12 @@ public class GraphQLMethodBinding extends BaseMethodBinding<String> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
if (Constants.OPERATION_NAME_GRAPHQL.equals(theRequest.getOperation())) {
|
||||
return true;
|
||||
return MethodMatchEnum.EXACT;
|
||||
}
|
||||
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -51,7 +51,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
|
||||
private final Integer myIdParamIndex;
|
||||
private final RestOperationTypeEnum myResourceOperationType;
|
||||
private String myResourceName;
|
||||
private final String myResourceName;
|
||||
|
||||
public HistoryMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
|
||||
super(toReturnType(theMethod, theProvider), theMethod, theContext, theProvider);
|
||||
|
@ -105,30 +105,36 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
|
||||
// ObjectUtils.equals is replaced by a JDK7 method..
|
||||
@Override
|
||||
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
if (!Constants.PARAM_HISTORY.equals(theRequest.getOperation())) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
if (theRequest.getResourceName() == null) {
|
||||
return myResourceOperationType == RestOperationTypeEnum.HISTORY_SYSTEM;
|
||||
if (myResourceOperationType == RestOperationTypeEnum.HISTORY_SYSTEM) {
|
||||
return MethodMatchEnum.EXACT;
|
||||
} else {
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
}
|
||||
if (!StringUtils.equals(theRequest.getResourceName(), myResourceName)) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
|
||||
boolean haveIdParam = theRequest.getId() != null && !theRequest.getId().isEmpty();
|
||||
boolean wantIdParam = myIdParamIndex != null;
|
||||
if (haveIdParam != wantIdParam) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
|
||||
if (theRequest.getId() == null) {
|
||||
return myResourceOperationType == RestOperationTypeEnum.HISTORY_TYPE;
|
||||
if (myResourceOperationType != RestOperationTypeEnum.HISTORY_TYPE) {
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
} else if (theRequest.getId().hasVersionIdPart()) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
|
||||
return true;
|
||||
return MethodMatchEnum.EXACT;
|
||||
}
|
||||
|
||||
|
||||
|
@ -179,7 +185,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
}
|
||||
if (isBlank(nextResource.getIdElement().getVersionIdPart()) && nextResource instanceof IResource) {
|
||||
//TODO: Use of a deprecated method should be resolved.
|
||||
IdDt versionId = (IdDt) ResourceMetadataKeyEnum.VERSION_ID.get((IResource) nextResource);
|
||||
IdDt versionId = ResourceMetadataKeyEnum.VERSION_ID.get((IResource) nextResource);
|
||||
if (versionId == null || versionId.isEmpty()) {
|
||||
throw new InternalErrorException("Server provided resource at index " + index + " with no Version ID set (using IResource#setId(IdDt))");
|
||||
}
|
||||
|
|
|
@ -106,6 +106,11 @@ class IncludeParameter extends BaseQueryParameter {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsRepetition() {
|
||||
return myInstantiableCollectionType != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handlesMissing() {
|
||||
return true;
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package ca.uhn.fhir.rest.server.method;
|
||||
|
||||
public enum MethodMatchEnum {
|
||||
|
||||
// Order these from worst to best!
|
||||
|
||||
NONE,
|
||||
APPROXIMATE,
|
||||
EXACT;
|
||||
|
||||
public MethodMatchEnum weakerOf(MethodMatchEnum theOther) {
|
||||
if (this.ordinal() < theOther.ordinal()) {
|
||||
return this;
|
||||
} else {
|
||||
return theOther;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -36,7 +36,6 @@ import ca.uhn.fhir.rest.server.method.ResourceParameter.Mode;
|
|||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.util.ParametersUtil;
|
||||
import ca.uhn.fhir.util.ReflectionUtil;
|
||||
import ca.uhn.fhir.util.ValidateUtil;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
|
@ -144,7 +143,7 @@ public class MethodUtil {
|
|||
parameter.setRequired(true);
|
||||
parameter.setDeclaredTypes(((RequiredParam) nextAnnotation).targetTypes());
|
||||
parameter.setCompositeTypes(((RequiredParam) nextAnnotation).compositeTypes());
|
||||
parameter.setChainlists(((RequiredParam) nextAnnotation).chainWhitelist(), ((RequiredParam) nextAnnotation).chainBlacklist());
|
||||
parameter.setChainLists(((RequiredParam) nextAnnotation).chainWhitelist(), ((RequiredParam) nextAnnotation).chainBlacklist());
|
||||
parameter.setType(theContext, parameterType, innerCollectionType, outerCollectionType);
|
||||
MethodUtil.extractDescription(parameter, annotations);
|
||||
param = parameter;
|
||||
|
@ -154,12 +153,12 @@ public class MethodUtil {
|
|||
parameter.setRequired(false);
|
||||
parameter.setDeclaredTypes(((OptionalParam) nextAnnotation).targetTypes());
|
||||
parameter.setCompositeTypes(((OptionalParam) nextAnnotation).compositeTypes());
|
||||
parameter.setChainlists(((OptionalParam) nextAnnotation).chainWhitelist(), ((OptionalParam) nextAnnotation).chainBlacklist());
|
||||
parameter.setChainLists(((OptionalParam) nextAnnotation).chainWhitelist(), ((OptionalParam) nextAnnotation).chainBlacklist());
|
||||
parameter.setType(theContext, parameterType, innerCollectionType, outerCollectionType);
|
||||
MethodUtil.extractDescription(parameter, annotations);
|
||||
param = parameter;
|
||||
} else if (nextAnnotation instanceof RawParam) {
|
||||
param = new RawParamsParmeter(parameters);
|
||||
param = new RawParamsParameter(parameters);
|
||||
} else if (nextAnnotation instanceof IncludeParam) {
|
||||
Class<? extends Collection<Include>> instantiableCollectionType;
|
||||
Class<?> specType;
|
||||
|
|
|
@ -226,43 +226,43 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
if (isBlank(theRequest.getOperation())) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
|
||||
if (!myName.equals(theRequest.getOperation())) {
|
||||
if (!myName.equals(WILDCARD_NAME)) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
if (getResourceName() == null) {
|
||||
if (isNotBlank(theRequest.getResourceName())) {
|
||||
if (!isGlobalMethod()) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (getResourceName() != null && !getResourceName().equals(theRequest.getResourceName())) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
|
||||
RequestTypeEnum requestType = theRequest.getRequestType();
|
||||
if (requestType != RequestTypeEnum.GET && requestType != RequestTypeEnum.POST) {
|
||||
// Operations can only be invoked with GET and POST
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
|
||||
boolean requestHasId = theRequest.getId() != null;
|
||||
if (requestHasId) {
|
||||
return myCanOperateAtInstanceLevel;
|
||||
return myCanOperateAtInstanceLevel ? MethodMatchEnum.EXACT : MethodMatchEnum.NONE;
|
||||
}
|
||||
if (isNotBlank(theRequest.getResourceName())) {
|
||||
return myCanOperateAtTypeLevel;
|
||||
return myCanOperateAtTypeLevel ? MethodMatchEnum.EXACT : MethodMatchEnum.NONE;
|
||||
}
|
||||
return myCanOperateAtServerLevel;
|
||||
return myCanOperateAtServerLevel ? MethodMatchEnum.EXACT : MethodMatchEnum.NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -174,12 +174,16 @@ public class PageMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
String[] pageId = theRequest.getParameters().get(Constants.PARAM_PAGINGACTION);
|
||||
if (pageId == null || pageId.length == 0 || isBlank(pageId[0])) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
return theRequest.getRequestType() == RequestTypeEnum.GET;
|
||||
if (theRequest.getRequestType() != RequestTypeEnum.GET) {
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
|
||||
return MethodMatchEnum.EXACT;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -80,9 +80,9 @@ public class PatchMethodBinding extends BaseOutcomeReturningMethodBindingWithRes
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
boolean retVal = super.incomingServerRequestMatchesMethod(theRequest);
|
||||
if (retVal) {
|
||||
public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
MethodMatchEnum retVal = super.incomingServerRequestMatchesMethod(theRequest);
|
||||
if (retVal.ordinal() > MethodMatchEnum.NONE.ordinal()) {
|
||||
PatchTypeParameter.getTypeForRequestOrThrowInvalidRequestException(theRequest);
|
||||
}
|
||||
return retVal;
|
||||
|
|
|
@ -35,11 +35,11 @@ import ca.uhn.fhir.rest.param.QualifierDetails;
|
|||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
public class RawParamsParmeter implements IParameter {
|
||||
public class RawParamsParameter implements IParameter {
|
||||
|
||||
private final List<IParameter> myAllMethodParameters;
|
||||
|
||||
public RawParamsParmeter(List<IParameter> theParameters) {
|
||||
public RawParamsParameter(List<IParameter> theParameters) {
|
||||
myAllMethodParameters = theParameters;
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ public class RawParamsParmeter implements IParameter {
|
|||
continue;
|
||||
}
|
||||
|
||||
QualifierDetails qualifiers = SearchMethodBinding.extractQualifiersFromParameterName(nextName);
|
||||
QualifierDetails qualifiers = QualifierDetails.extractQualifiersFromParameterName(nextName);
|
||||
|
||||
boolean alreadyCaptured = false;
|
||||
for (IParameter nextParameter : myAllMethodParameters) {
|
||||
|
@ -70,7 +70,7 @@ public class RawParamsParmeter implements IParameter {
|
|||
|
||||
if (!alreadyCaptured) {
|
||||
if (retVal == null) {
|
||||
retVal = new HashMap<String, List<String>>();
|
||||
retVal = new HashMap<>();
|
||||
}
|
||||
retVal.put(nextName, Arrays.asList(theRequest.getParameters().get(nextName)));
|
||||
}
|
|
@ -114,40 +114,40 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
if (!theRequest.getResourceName().equals(getResourceName())) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
for (String next : theRequest.getParameters().keySet()) {
|
||||
if (!next.startsWith("_")) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
}
|
||||
if (theRequest.getId() == null) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
if (mySupportsVersion == false) {
|
||||
if (theRequest.getId().hasVersionIdPart()) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
}
|
||||
if (isNotBlank(theRequest.getCompartmentName())) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
if (theRequest.getRequestType() != RequestTypeEnum.GET && theRequest.getRequestType() != RequestTypeEnum.HEAD ) {
|
||||
ourLog.trace("Method {} doesn't match because request type is not GET or HEAD: {}", theRequest.getId(), theRequest.getRequestType());
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
if (Constants.PARAM_HISTORY.equals(theRequest.getOperation())) {
|
||||
if (mySupportsVersion == false) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
} else if (theRequest.getId().hasVersionIdPart() == false) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
} else if (!StringUtils.isBlank(theRequest.getOperation())) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
return true;
|
||||
return MethodMatchEnum.EXACT;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -41,7 +41,11 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
|
|||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
@ -61,11 +65,13 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
}
|
||||
|
||||
private final String myResourceProviderResourceName;
|
||||
private String myCompartmentName;
|
||||
private final List<String> myRequiredParamNames;
|
||||
private final List<String> myOptionalParamNames;
|
||||
private final String myCompartmentName;
|
||||
private String myDescription;
|
||||
private Integer myIdParamIndex;
|
||||
private String myQueryName;
|
||||
private boolean myAllowUnknownParams;
|
||||
private final Integer myIdParamIndex;
|
||||
private final String myQueryName;
|
||||
private final boolean myAllowUnknownParams;
|
||||
|
||||
public SearchMethodBinding(Class<? extends IBaseResource> theReturnResourceType, Class<? extends IBaseResource> theResourceProviderResourceType, Method theMethod, FhirContext theContext, Object theProvider) {
|
||||
super(theReturnResourceType, theMethod, theContext, theProvider);
|
||||
|
@ -98,6 +104,17 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
this.myResourceProviderResourceName = null;
|
||||
}
|
||||
|
||||
myRequiredParamNames = getQueryParameters()
|
||||
.stream()
|
||||
.filter(t -> t.isRequired())
|
||||
.map(t -> t.getName())
|
||||
.collect(Collectors.toList());
|
||||
myOptionalParamNames = getQueryParameters()
|
||||
.stream()
|
||||
.filter(t -> !t.isRequired())
|
||||
.map(t -> t.getName())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
|
@ -129,122 +146,129 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
|
||||
if (theRequest.getId() != null && myIdParamIndex == null) {
|
||||
ourLog.trace("Method {} doesn't match because ID is not null: {}", getMethod(), theRequest.getId());
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
if (theRequest.getRequestType() == RequestTypeEnum.GET && theRequest.getOperation() != null && !Constants.PARAM_SEARCH.equals(theRequest.getOperation())) {
|
||||
ourLog.trace("Method {} doesn't match because request type is GET but operation is not null: {}", theRequest.getId(), theRequest.getOperation());
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
if (theRequest.getRequestType() == RequestTypeEnum.POST && !Constants.PARAM_SEARCH.equals(theRequest.getOperation())) {
|
||||
ourLog.trace("Method {} doesn't match because request type is POST but operation is not _search: {}", theRequest.getId(), theRequest.getOperation());
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
if (theRequest.getRequestType() != RequestTypeEnum.GET && theRequest.getRequestType() != RequestTypeEnum.POST) {
|
||||
ourLog.trace("Method {} doesn't match because request type is {}", getMethod(), theRequest.getRequestType());
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
if (!StringUtils.equals(myCompartmentName, theRequest.getCompartmentName())) {
|
||||
ourLog.trace("Method {} doesn't match because it is for compartment {} but request is compartment {}", getMethod(), myCompartmentName, theRequest.getCompartmentName());
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
if (theRequest.getParameters().get(Constants.PARAM_PAGINGACTION) != null) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
|
||||
// This is used to track all the parameters so we can reject queries that
|
||||
// have additional params we don't understand
|
||||
Set<String> methodParamsTemp = new HashSet<>();
|
||||
|
||||
Set<String> unqualifiedNames = theRequest.getUnqualifiedToQualifiedNames().keySet();
|
||||
Set<String> qualifiedParamNames = theRequest.getParameters().keySet();
|
||||
for (IParameter nextParameter : getParameters()) {
|
||||
if (!(nextParameter instanceof BaseQueryParameter)) {
|
||||
continue;
|
||||
}
|
||||
BaseQueryParameter nextQueryParameter = (BaseQueryParameter) nextParameter;
|
||||
String name = nextQueryParameter.getName();
|
||||
if (nextQueryParameter.isRequired()) {
|
||||
|
||||
if (qualifiedParamNames.contains(name)) {
|
||||
QualifierDetails qualifiers = extractQualifiersFromParameterName(name);
|
||||
if (qualifiers.passes(nextQueryParameter.getQualifierWhitelist(), nextQueryParameter.getQualifierBlacklist())) {
|
||||
methodParamsTemp.add(name);
|
||||
}
|
||||
}
|
||||
if (unqualifiedNames.contains(name)) {
|
||||
List<String> qualifiedNames = theRequest.getUnqualifiedToQualifiedNames().get(name);
|
||||
qualifiedNames = processWhitelistAndBlacklist(qualifiedNames, nextQueryParameter.getQualifierWhitelist(), nextQueryParameter.getQualifierBlacklist());
|
||||
methodParamsTemp.addAll(qualifiedNames);
|
||||
}
|
||||
if (!qualifiedParamNames.contains(name) && !unqualifiedNames.contains(name)) {
|
||||
ourLog.trace("Method {} doesn't match param '{}' is not present", getMethod().getName(), name);
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
if (qualifiedParamNames.contains(name)) {
|
||||
QualifierDetails qualifiers = extractQualifiersFromParameterName(name);
|
||||
if (qualifiers.passes(nextQueryParameter.getQualifierWhitelist(), nextQueryParameter.getQualifierBlacklist())) {
|
||||
methodParamsTemp.add(name);
|
||||
}
|
||||
}
|
||||
if (unqualifiedNames.contains(name)) {
|
||||
List<String> qualifiedNames = theRequest.getUnqualifiedToQualifiedNames().get(name);
|
||||
qualifiedNames = processWhitelistAndBlacklist(qualifiedNames, nextQueryParameter.getQualifierWhitelist(), nextQueryParameter.getQualifierBlacklist());
|
||||
methodParamsTemp.addAll(qualifiedNames);
|
||||
}
|
||||
if (!qualifiedParamNames.contains(name)) {
|
||||
methodParamsTemp.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (myQueryName != null) {
|
||||
String[] queryNameValues = theRequest.getParameters().get(Constants.PARAM_QUERY);
|
||||
if (queryNameValues != null && StringUtils.isNotBlank(queryNameValues[0])) {
|
||||
String queryName = queryNameValues[0];
|
||||
if (!myQueryName.equals(queryName)) {
|
||||
ourLog.trace("Query name does not match {}", myQueryName);
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
methodParamsTemp.add(Constants.PARAM_QUERY);
|
||||
} else {
|
||||
ourLog.trace("Query name does not match {}", myQueryName);
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
} else {
|
||||
String[] queryNameValues = theRequest.getParameters().get(Constants.PARAM_QUERY);
|
||||
if (queryNameValues != null && StringUtils.isNotBlank(queryNameValues[0])) {
|
||||
ourLog.trace("Query has name");
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
}
|
||||
for (String next : theRequest.getParameters().keySet()) {
|
||||
if (next.startsWith("_") && !SPECIAL_SEARCH_PARAMS.contains(truncModifierPart(next))) {
|
||||
methodParamsTemp.add(next);
|
||||
}
|
||||
}
|
||||
Set<String> keySet = theRequest.getParameters().keySet();
|
||||
|
||||
if (myAllowUnknownParams == false) {
|
||||
for (String next : keySet) {
|
||||
if (!methodParamsTemp.contains(next)) {
|
||||
return false;
|
||||
Set<String> unqualifiedNames = theRequest.getUnqualifiedToQualifiedNames().keySet();
|
||||
Set<String> qualifiedParamNames = theRequest.getParameters().keySet();
|
||||
|
||||
MethodMatchEnum retVal = MethodMatchEnum.EXACT;
|
||||
for (String nextRequestParam : theRequest.getParameters().keySet()) {
|
||||
String nextUnqualifiedRequestParam = ParameterUtil.stripModifierPart(nextRequestParam);
|
||||
if (nextRequestParam.startsWith("_") && !SPECIAL_SEARCH_PARAMS.contains(nextUnqualifiedRequestParam)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean parameterMatches = false;
|
||||
boolean approx = false;
|
||||
for (BaseQueryParameter nextMethodParam : getQueryParameters()) {
|
||||
|
||||
if (nextRequestParam.equals(nextMethodParam.getName())) {
|
||||
QualifierDetails qualifiers = QualifierDetails.extractQualifiersFromParameterName(nextRequestParam);
|
||||
if (qualifiers.passes(nextMethodParam.getQualifierWhitelist(), nextMethodParam.getQualifierBlacklist())) {
|
||||
parameterMatches = true;
|
||||
}
|
||||
} else if (nextUnqualifiedRequestParam.equals(nextMethodParam.getName())) {
|
||||
List<String> qualifiedNames = theRequest.getUnqualifiedToQualifiedNames().get(nextUnqualifiedRequestParam);
|
||||
if (passesWhitelistAndBlacklist(qualifiedNames, nextMethodParam.getQualifierWhitelist(), nextMethodParam.getQualifierBlacklist())) {
|
||||
parameterMatches = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Repetitions supplied by URL but not supported by this parameter
|
||||
if (theRequest.getParameters().get(nextRequestParam).length > 1 != nextMethodParam.supportsRepetition()) {
|
||||
approx = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (parameterMatches) {
|
||||
|
||||
if (approx) {
|
||||
retVal = retVal.weakerOf(MethodMatchEnum.APPROXIMATE);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (myAllowUnknownParams) {
|
||||
retVal = retVal.weakerOf(MethodMatchEnum.APPROXIMATE);
|
||||
} else {
|
||||
retVal = retVal.weakerOf(MethodMatchEnum.NONE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (retVal == MethodMatchEnum.NONE) {
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (retVal != MethodMatchEnum.NONE) {
|
||||
for (String nextRequiredParamName : myRequiredParamNames) {
|
||||
if (!qualifiedParamNames.contains(nextRequiredParamName)) {
|
||||
if (!unqualifiedNames.contains(nextRequiredParamName)) {
|
||||
retVal = MethodMatchEnum.NONE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private String truncModifierPart(String param) {
|
||||
int indexOfSeparator = param.indexOf(":");
|
||||
if (indexOfSeparator != -1) {
|
||||
return param.substring(0, indexOfSeparator);
|
||||
if (retVal != MethodMatchEnum.NONE) {
|
||||
for (String nextRequiredParamName : myOptionalParamNames) {
|
||||
if (!qualifiedParamNames.contains(nextRequiredParamName)) {
|
||||
if (!unqualifiedNames.contains(nextRequiredParamName)) {
|
||||
retVal = retVal.weakerOf(MethodMatchEnum.APPROXIMATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return param;
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -264,19 +288,18 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
return false;
|
||||
}
|
||||
|
||||
private List<String> processWhitelistAndBlacklist(List<String> theQualifiedNames, Set<String> theQualifierWhitelist, Set<String> theQualifierBlacklist) {
|
||||
|
||||
private boolean passesWhitelistAndBlacklist(List<String> theQualifiedNames, Set<String> theQualifierWhitelist, Set<String> theQualifierBlacklist) {
|
||||
if (theQualifierWhitelist == null && theQualifierBlacklist == null) {
|
||||
return theQualifiedNames;
|
||||
return true;
|
||||
}
|
||||
ArrayList<String> retVal = new ArrayList<>(theQualifiedNames.size());
|
||||
for (String next : theQualifiedNames) {
|
||||
QualifierDetails qualifiers = extractQualifiersFromParameterName(next);
|
||||
QualifierDetails qualifiers = QualifierDetails.extractQualifiersFromParameterName(next);
|
||||
if (!qualifiers.passes(theQualifierWhitelist, theQualifierBlacklist)) {
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
retVal.add(next);
|
||||
}
|
||||
return retVal;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -284,52 +307,5 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
return getMethod().toString();
|
||||
}
|
||||
|
||||
public static QualifierDetails extractQualifiersFromParameterName(String theParamName) {
|
||||
QualifierDetails retVal = new QualifierDetails();
|
||||
if (theParamName == null || theParamName.length() == 0) {
|
||||
return retVal;
|
||||
}
|
||||
|
||||
int dotIdx = -1;
|
||||
int colonIdx = -1;
|
||||
for (int idx = 0; idx < theParamName.length(); idx++) {
|
||||
char nextChar = theParamName.charAt(idx);
|
||||
if (nextChar == '.' && dotIdx == -1) {
|
||||
dotIdx = idx;
|
||||
} else if (nextChar == ':' && colonIdx == -1) {
|
||||
colonIdx = idx;
|
||||
}
|
||||
}
|
||||
|
||||
if (dotIdx != -1 && colonIdx != -1) {
|
||||
if (dotIdx < colonIdx) {
|
||||
retVal.setDotQualifier(theParamName.substring(dotIdx, colonIdx));
|
||||
retVal.setColonQualifier(theParamName.substring(colonIdx));
|
||||
retVal.setParamName(theParamName.substring(0, dotIdx));
|
||||
retVal.setWholeQualifier(theParamName.substring(dotIdx));
|
||||
} else {
|
||||
retVal.setColonQualifier(theParamName.substring(colonIdx, dotIdx));
|
||||
retVal.setDotQualifier(theParamName.substring(dotIdx));
|
||||
retVal.setParamName(theParamName.substring(0, colonIdx));
|
||||
retVal.setWholeQualifier(theParamName.substring(colonIdx));
|
||||
}
|
||||
} else if (dotIdx != -1) {
|
||||
retVal.setDotQualifier(theParamName.substring(dotIdx));
|
||||
retVal.setParamName(theParamName.substring(0, dotIdx));
|
||||
retVal.setWholeQualifier(theParamName.substring(dotIdx));
|
||||
} else if (colonIdx != -1) {
|
||||
retVal.setColonQualifier(theParamName.substring(colonIdx));
|
||||
retVal.setParamName(theParamName.substring(0, colonIdx));
|
||||
retVal.setWholeQualifier(theParamName.substring(colonIdx));
|
||||
} else {
|
||||
retVal.setParamName(theParamName);
|
||||
retVal.setColonQualifier(null);
|
||||
retVal.setDotQualifier(null);
|
||||
retVal.setWholeQualifier(null);
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -42,8 +42,8 @@ import ca.uhn.fhir.util.ReflectionUtil;
|
|||
public class SearchParameter extends BaseQueryParameter {
|
||||
|
||||
private static final String EMPTY_STRING = "";
|
||||
private static HashMap<RestSearchParameterTypeEnum, Set<String>> ourParamQualifiers;
|
||||
private static HashMap<Class<?>, RestSearchParameterTypeEnum> ourParamTypes;
|
||||
private static final HashMap<RestSearchParameterTypeEnum, Set<String>> ourParamQualifiers;
|
||||
private static final HashMap<Class<?>, RestSearchParameterTypeEnum> ourParamTypes;
|
||||
static final String QUALIFIER_ANY_TYPE = ":*";
|
||||
|
||||
static {
|
||||
|
@ -114,6 +114,7 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
private Set<String> myQualifierWhitelist;
|
||||
private boolean myRequired;
|
||||
private Class<?> myType;
|
||||
private boolean mySupportsRepetition = false;
|
||||
|
||||
public SearchParameter() {
|
||||
}
|
||||
|
@ -202,17 +203,17 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
return myParamBinder.parse(theContext, getName(), theString);
|
||||
}
|
||||
|
||||
public void setChainlists(String[] theChainWhitelist, String[] theChainBlacklist) {
|
||||
public void setChainLists(String[] theChainWhitelist, String[] theChainBlacklist) {
|
||||
myQualifierWhitelist = new HashSet<>(theChainWhitelist.length);
|
||||
myQualifierWhitelist.add(QUALIFIER_ANY_TYPE);
|
||||
|
||||
for (int i = 0; i < theChainWhitelist.length; i++) {
|
||||
if (theChainWhitelist[i].equals(OptionalParam.ALLOW_CHAIN_ANY)) {
|
||||
for (String nextChain : theChainWhitelist) {
|
||||
if (nextChain.equals(OptionalParam.ALLOW_CHAIN_ANY)) {
|
||||
myQualifierWhitelist.add('.' + OptionalParam.ALLOW_CHAIN_ANY);
|
||||
} else if (theChainWhitelist[i].equals(EMPTY_STRING)) {
|
||||
} else if (nextChain.equals(EMPTY_STRING)) {
|
||||
myQualifierWhitelist.add(".");
|
||||
} else {
|
||||
myQualifierWhitelist.add('.' + theChainWhitelist[i]);
|
||||
myQualifierWhitelist.add('.' + nextChain);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -248,42 +249,48 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
this.myRequired = required;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsRepetition() {
|
||||
return mySupportsRepetition;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setType(FhirContext theContext, final Class<?> type, Class<? extends Collection<?>> theInnerCollectionType, Class<? extends Collection<?>> theOuterCollectionType) {
|
||||
public void setType(FhirContext theContext, final Class<?> theType, Class<? extends Collection<?>> theInnerCollectionType, Class<? extends Collection<?>> theOuterCollectionType) {
|
||||
|
||||
|
||||
this.myType = type;
|
||||
if (IQueryParameterType.class.isAssignableFrom(type)) {
|
||||
myParamBinder = new QueryParameterTypeBinder((Class<? extends IQueryParameterType>) type, myCompositeTypes);
|
||||
} else if (IQueryParameterOr.class.isAssignableFrom(type)) {
|
||||
myParamBinder = new QueryParameterOrBinder((Class<? extends IQueryParameterOr<?>>) type, myCompositeTypes);
|
||||
} else if (IQueryParameterAnd.class.isAssignableFrom(type)) {
|
||||
myParamBinder = new QueryParameterAndBinder((Class<? extends IQueryParameterAnd<?>>) type, myCompositeTypes);
|
||||
} else if (String.class.equals(type)) {
|
||||
this.myType = theType;
|
||||
if (IQueryParameterType.class.isAssignableFrom(theType)) {
|
||||
myParamBinder = new QueryParameterTypeBinder((Class<? extends IQueryParameterType>) theType, myCompositeTypes);
|
||||
} else if (IQueryParameterOr.class.isAssignableFrom(theType)) {
|
||||
myParamBinder = new QueryParameterOrBinder((Class<? extends IQueryParameterOr<?>>) theType, myCompositeTypes);
|
||||
} else if (IQueryParameterAnd.class.isAssignableFrom(theType)) {
|
||||
myParamBinder = new QueryParameterAndBinder((Class<? extends IQueryParameterAnd<?>>) theType, myCompositeTypes);
|
||||
mySupportsRepetition = true;
|
||||
} else if (String.class.equals(theType)) {
|
||||
myParamBinder = new StringBinder();
|
||||
myParamType = RestSearchParameterTypeEnum.STRING;
|
||||
} else if (Date.class.equals(type)) {
|
||||
} else if (Date.class.equals(theType)) {
|
||||
myParamBinder = new DateBinder();
|
||||
myParamType = RestSearchParameterTypeEnum.DATE;
|
||||
} else if (Calendar.class.equals(type)) {
|
||||
} else if (Calendar.class.equals(theType)) {
|
||||
myParamBinder = new CalendarBinder();
|
||||
myParamType = RestSearchParameterTypeEnum.DATE;
|
||||
} else if (IPrimitiveType.class.isAssignableFrom(type) && ReflectionUtil.isInstantiable(type)) {
|
||||
RuntimePrimitiveDatatypeDefinition def = (RuntimePrimitiveDatatypeDefinition) theContext.getElementDefinition((Class<? extends IPrimitiveType<?>>) type);
|
||||
} else if (IPrimitiveType.class.isAssignableFrom(theType) && ReflectionUtil.isInstantiable(theType)) {
|
||||
RuntimePrimitiveDatatypeDefinition def = (RuntimePrimitiveDatatypeDefinition) theContext.getElementDefinition((Class<? extends IPrimitiveType<?>>) theType);
|
||||
if (def.getNativeType() != null) {
|
||||
if (def.getNativeType().equals(Date.class)) {
|
||||
myParamBinder = new FhirPrimitiveBinder((Class<IPrimitiveType<?>>) type);
|
||||
myParamBinder = new FhirPrimitiveBinder((Class<IPrimitiveType<?>>) theType);
|
||||
myParamType = RestSearchParameterTypeEnum.DATE;
|
||||
} else if (def.getNativeType().equals(String.class)) {
|
||||
myParamBinder = new FhirPrimitiveBinder((Class<IPrimitiveType<?>>) type);
|
||||
myParamBinder = new FhirPrimitiveBinder((Class<IPrimitiveType<?>>) theType);
|
||||
myParamType = RestSearchParameterTypeEnum.STRING;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new ConfigurationException("Unsupported data type for parameter: " + type.getCanonicalName());
|
||||
throw new ConfigurationException("Unsupported data type for parameter: " + theType.getCanonicalName());
|
||||
}
|
||||
|
||||
RestSearchParameterTypeEnum typeEnum = ourParamTypes.get(type);
|
||||
RestSearchParameterTypeEnum typeEnum = ourParamTypes.get(theType);
|
||||
if (typeEnum != null) {
|
||||
Set<String> builtInQualifiers = ourParamQualifiers.get(typeEnum);
|
||||
if (builtInQualifiers != null) {
|
||||
|
@ -304,22 +311,22 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
|
||||
if (myParamType != null) {
|
||||
// ok
|
||||
} else if (StringDt.class.isAssignableFrom(type)) {
|
||||
} else if (StringDt.class.isAssignableFrom(theType)) {
|
||||
myParamType = RestSearchParameterTypeEnum.STRING;
|
||||
} else if (BaseIdentifierDt.class.isAssignableFrom(type)) {
|
||||
} else if (BaseIdentifierDt.class.isAssignableFrom(theType)) {
|
||||
myParamType = RestSearchParameterTypeEnum.TOKEN;
|
||||
} else if (BaseQuantityDt.class.isAssignableFrom(type)) {
|
||||
} else if (BaseQuantityDt.class.isAssignableFrom(theType)) {
|
||||
myParamType = RestSearchParameterTypeEnum.QUANTITY;
|
||||
} else if (ReferenceParam.class.isAssignableFrom(type)) {
|
||||
} else if (ReferenceParam.class.isAssignableFrom(theType)) {
|
||||
myParamType = RestSearchParameterTypeEnum.REFERENCE;
|
||||
} else if (HasParam.class.isAssignableFrom(type)) {
|
||||
} else if (HasParam.class.isAssignableFrom(theType)) {
|
||||
myParamType = RestSearchParameterTypeEnum.STRING;
|
||||
} else {
|
||||
throw new ConfigurationException("Unknown search parameter type: " + type);
|
||||
throw new ConfigurationException("Unknown search parameter theType: " + theType);
|
||||
}
|
||||
|
||||
// NB: Once this is enabled, we should return true from handlesMissing if
|
||||
// it's a collection type
|
||||
// it's a collection theType
|
||||
// if (theInnerCollectionType != null) {
|
||||
// this.parser = new CollectionBinder(this.parser, theInnerCollectionType);
|
||||
// }
|
||||
|
|
|
@ -22,17 +22,13 @@ package ca.uhn.fhir.rest.server.method;
|
|||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||
import ca.uhn.fhir.rest.annotation.Transaction;
|
||||
import ca.uhn.fhir.rest.annotation.TransactionParam;
|
||||
|
@ -92,17 +88,17 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
if (theRequest.getRequestType() != RequestTypeEnum.POST) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
if (isNotBlank(theRequest.getOperation())) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
if (isNotBlank(theRequest.getResourceName())) {
|
||||
return false;
|
||||
return MethodMatchEnum.NONE;
|
||||
}
|
||||
return true;
|
||||
return MethodMatchEnum.EXACT;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package ca.uhn.fhir.rest.server.method;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class MethodMatchEnumTest {
|
||||
|
||||
@Test
|
||||
public void testOrder() {
|
||||
assertEquals(0, MethodMatchEnum.NONE.ordinal());
|
||||
assertEquals(1, MethodMatchEnum.APPROXIMATE.ordinal());
|
||||
assertEquals(2, MethodMatchEnum.EXACT.ordinal());
|
||||
}
|
||||
|
||||
}
|
|
@ -20,6 +20,7 @@ import org.mockito.junit.MockitoJUnitRunner;
|
|||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
|
@ -56,17 +57,17 @@ public class ReadMethodBindingTest {
|
|||
// Read
|
||||
ReadMethodBinding binding = createBinding(new MyProvider());
|
||||
when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123"));
|
||||
assertTrue(binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||
assertEquals(MethodMatchEnum.EXACT, binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||
|
||||
// VRead
|
||||
when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123/_history/123"));
|
||||
assertFalse(binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||
assertEquals(MethodMatchEnum.NONE, binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||
|
||||
// Type history
|
||||
when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123"));
|
||||
when(myRequestDetails.getResourceName()).thenReturn("Patient");
|
||||
when(myRequestDetails.getOperation()).thenReturn("_history");
|
||||
assertFalse(binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||
assertEquals(MethodMatchEnum.NONE, binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||
|
||||
}
|
||||
|
||||
|
@ -89,27 +90,27 @@ public class ReadMethodBindingTest {
|
|||
ReadMethodBinding binding = createBinding(new MyProvider());
|
||||
when(myRequestDetails.getResourceName()).thenReturn("Observation");
|
||||
when(myRequestDetails.getId()).thenReturn(new IdDt("Observation/123"));
|
||||
assertFalse(binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||
assertEquals(MethodMatchEnum.NONE, binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||
|
||||
// Read
|
||||
when(myRequestDetails.getResourceName()).thenReturn("Patient");
|
||||
when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123"));
|
||||
assertTrue(binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||
assertEquals(MethodMatchEnum.EXACT, binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||
|
||||
// VRead
|
||||
when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123/_history/123"));
|
||||
when(myRequestDetails.getOperation()).thenReturn("_history");
|
||||
assertTrue(binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||
assertEquals(MethodMatchEnum.EXACT, binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||
|
||||
// Some other operation
|
||||
when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123/_history/123"));
|
||||
when(myRequestDetails.getOperation()).thenReturn("$foo");
|
||||
assertFalse(binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||
assertEquals(MethodMatchEnum.NONE, binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||
|
||||
// History operation
|
||||
when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123"));
|
||||
when(myRequestDetails.getOperation()).thenReturn("_history");
|
||||
assertFalse(binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||
assertEquals(MethodMatchEnum.NONE, binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||
|
||||
}
|
||||
|
||||
|
@ -130,7 +131,7 @@ public class ReadMethodBindingTest {
|
|||
when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123"));
|
||||
|
||||
ReadMethodBinding binding = createBinding(new MyProvider());
|
||||
assertFalse(binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||
assertEquals(MethodMatchEnum.NONE, binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||
}
|
||||
|
||||
public ReadMethodBinding createBinding(Object theProvider) throws NoSuchMethodException {
|
||||
|
|
|
@ -42,39 +42,39 @@ public class SearchMethodBindingTest {
|
|||
public void methodShouldNotMatchWhenUnderscoreQueryParameter() throws NoSuchMethodException {
|
||||
Assert.assertThat(getBinding("param", String.class).incomingServerRequestMatchesMethod(
|
||||
mockSearchRequest(ImmutableMap.of("param", new String[]{"value"}, "_include", new String[]{"test"}))),
|
||||
Matchers.is(false));
|
||||
Matchers.is(MethodMatchEnum.NONE));
|
||||
Assert.assertThat(getBinding("paramAndTest", String.class, String.class).incomingServerRequestMatchesMethod(
|
||||
mockSearchRequest(ImmutableMap.of("param", new String[]{"value"}, "_include", new String[]{"test"}))),
|
||||
Matchers.is(false));
|
||||
Matchers.is(MethodMatchEnum.NONE));
|
||||
Assert.assertThat(getBinding("paramAndUnderscoreTest", String.class, String.class).incomingServerRequestMatchesMethod(
|
||||
mockSearchRequest(ImmutableMap.of("param", new String[]{"value"}, "_include", new String[]{"test"}))),
|
||||
Matchers.is(false));
|
||||
Matchers.is(MethodMatchEnum.NONE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodShouldNotMatchWhenExtraQueryParameter() throws NoSuchMethodException {
|
||||
Assert.assertThat(getBinding("param", String.class).incomingServerRequestMatchesMethod(
|
||||
mockSearchRequest(ImmutableMap.of("param", new String[]{"value"}, "extra", new String[]{"test"}))),
|
||||
Matchers.is(false));
|
||||
Matchers.is(MethodMatchEnum.NONE));
|
||||
Assert.assertThat(getBinding("paramAndTest", String.class, String.class).incomingServerRequestMatchesMethod(
|
||||
mockSearchRequest(ImmutableMap.of("param", new String[]{"value"}, "extra", new String[]{"test"}))),
|
||||
Matchers.is(false));
|
||||
Matchers.is(MethodMatchEnum.NONE));
|
||||
Assert.assertThat(getBinding("paramAndUnderscoreTest", String.class, String.class).incomingServerRequestMatchesMethod(
|
||||
mockSearchRequest(ImmutableMap.of("param", new String[]{"value"}, "extra", new String[]{"test"}))),
|
||||
Matchers.is(false));
|
||||
Matchers.is(MethodMatchEnum.NONE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodMatchesOwnParams() throws NoSuchMethodException {
|
||||
Assert.assertThat(getBinding("param", String.class).incomingServerRequestMatchesMethod(
|
||||
mockSearchRequest(ImmutableMap.of("param", new String[]{"value"}))),
|
||||
Matchers.is(true));
|
||||
Matchers.is(MethodMatchEnum.EXACT));
|
||||
Assert.assertThat(getBinding("paramAndTest", String.class, String.class).incomingServerRequestMatchesMethod(
|
||||
mockSearchRequest(ImmutableMap.of("param", new String[]{"value"}, "test", new String[]{"test"}))),
|
||||
Matchers.is(true));
|
||||
Matchers.is(MethodMatchEnum.EXACT));
|
||||
Assert.assertThat(getBinding("paramAndUnderscoreTest", String.class, String.class).incomingServerRequestMatchesMethod(
|
||||
mockSearchRequest(ImmutableMap.of("param", new String[]{"value"}, "_test", new String[]{"test"}))),
|
||||
Matchers.is(true));
|
||||
Matchers.is(MethodMatchEnum.EXACT));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -83,10 +83,10 @@ public class SearchMethodBindingTest {
|
|||
ourLog.info("Testing binding: {}", binding);
|
||||
Assert.assertThat(binding.incomingServerRequestMatchesMethod(
|
||||
mockSearchRequest(ImmutableMap.of("refChainBlacklist.badChain", new String[]{"foo"}))),
|
||||
Matchers.is(false));
|
||||
Matchers.is(MethodMatchEnum.NONE));
|
||||
Assert.assertThat(binding.incomingServerRequestMatchesMethod(
|
||||
mockSearchRequest(ImmutableMap.of("refChainBlacklist.goodChain", new String[]{"foo"}))),
|
||||
Matchers.is(true));
|
||||
Matchers.is(MethodMatchEnum.EXACT));
|
||||
}
|
||||
|
||||
private SearchMethodBinding getBinding(String name, Class<?>... parameters) throws NoSuchMethodException {
|
||||
|
|
|
@ -156,53 +156,47 @@ public class ServerSearchDstu2Test {
|
|||
|
||||
public static class DummyPatientResourceProvider {
|
||||
|
||||
//@formatter:off
|
||||
@Search(allowUnknownParams=true)
|
||||
public List<IBaseResource> searchParam1(
|
||||
@RequiredParam(name = "param1") StringParam theParam) {
|
||||
ourLastMethod = "searchParam1";
|
||||
ourLastRef = theParam;
|
||||
|
||||
List<IBaseResource> retVal = new ArrayList<IBaseResource>();
|
||||
List<IBaseResource> retVal = new ArrayList<>();
|
||||
Patient patient = new Patient();
|
||||
patient.setId("123");
|
||||
patient.addName().addGiven("GIVEN");
|
||||
retVal.add(patient);
|
||||
return retVal;
|
||||
}
|
||||
//@formatter:on
|
||||
|
||||
//@formatter:off
|
||||
@Search(allowUnknownParams=true)
|
||||
public List<IBaseResource> searchParam2(
|
||||
@RequiredParam(name = "param2") StringParam theParam) {
|
||||
ourLastMethod = "searchParam2";
|
||||
ourLastRef = theParam;
|
||||
|
||||
List<IBaseResource> retVal = new ArrayList<IBaseResource>();
|
||||
List<IBaseResource> retVal = new ArrayList<>();
|
||||
Patient patient = new Patient();
|
||||
patient.setId("123");
|
||||
patient.addName().addGiven("GIVEN");
|
||||
retVal.add(patient);
|
||||
return retVal;
|
||||
}
|
||||
//@formatter:on
|
||||
|
||||
//@formatter:off
|
||||
@Search(allowUnknownParams=true)
|
||||
public List<IBaseResource> searchParam3(
|
||||
@RequiredParam(name = "param3") ReferenceParam theParam) {
|
||||
ourLastMethod = "searchParam3";
|
||||
ourLastRef2 = theParam;
|
||||
|
||||
List<IBaseResource> retVal = new ArrayList<IBaseResource>();
|
||||
List<IBaseResource> retVal = new ArrayList<>();
|
||||
Patient patient = new Patient();
|
||||
patient.setId("123");
|
||||
patient.addName().addGiven("GIVEN");
|
||||
retVal.add(patient);
|
||||
return retVal;
|
||||
}
|
||||
//@formatter:on
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -112,7 +112,6 @@ public class SearchCountParamDstu3Test {
|
|||
assertEquals("searchWithNoCountParam", ourLastMethod);
|
||||
assertEquals(null, ourLastParam);
|
||||
|
||||
//@formatter:off
|
||||
assertThat(responseContent, stringContainsInOrder(
|
||||
"<link>",
|
||||
"<relation value=\"self\"/>",
|
||||
|
@ -122,7 +121,6 @@ public class SearchCountParamDstu3Test {
|
|||
"<relation value=\"next\"/>",
|
||||
"<url value=\"http://localhost:" + ourPort + "?_getpages=", "&_getpagesoffset=2&_count=2&_bundletype=searchset\"/>",
|
||||
"</link>"));
|
||||
//@formatter:on
|
||||
|
||||
} finally {
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.rest.annotation.OptionalParam;
|
||||
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.param.DateParam;
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
import ca.uhn.fhir.rest.param.StringAndListParam;
|
||||
import ca.uhn.fhir.test.utilities.server.RestfulServerRule;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class SearchMethodPriorityTest {
|
||||
|
||||
@ClassRule
|
||||
public static RestfulServerRule ourServerRule = new RestfulServerRule(FhirVersionEnum.R4);
|
||||
|
||||
private String myLastMethod;
|
||||
private IGenericClient myClient;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
myLastMethod = null;
|
||||
myClient = ourServerRule.getFhirClient();
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
ourServerRule.getRestfulServer().unregisterAllProviders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDateRangeSelectedWhenMultipleParametersProvided() {
|
||||
ourServerRule.getRestfulServer().registerProviders(new DateStrengthsWithRequiredResourceProvider());
|
||||
|
||||
myClient
|
||||
.search()
|
||||
.forResource("Patient")
|
||||
.where(Patient.BIRTHDATE.after().day("2001-01-01"))
|
||||
.and(Patient.BIRTHDATE.before().day("2002-01-01"))
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
|
||||
assertEquals("findDateRangeParam", myLastMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDateRangeNotSelectedWhenSingleParameterProvided() {
|
||||
ourServerRule.getRestfulServer().registerProviders(new DateStrengthsWithRequiredResourceProvider());
|
||||
|
||||
myClient
|
||||
.search()
|
||||
.forResource("Patient")
|
||||
.where(Patient.BIRTHDATE.after().day("2001-01-01"))
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
|
||||
assertEquals("findDateParam", myLastMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyDateSearchProvidedWithNoParameters() {
|
||||
ourServerRule.getRestfulServer().registerProviders(new DateStrengthsWithRequiredResourceProvider());
|
||||
|
||||
myClient
|
||||
.search()
|
||||
.forResource("Patient")
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
|
||||
assertEquals("find", myLastMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringAndListSelectedWhenMultipleParametersProvided() {
|
||||
ourServerRule.getRestfulServer().registerProviders(new StringStrengthsWithOptionalResourceProvider());
|
||||
|
||||
myClient
|
||||
.search()
|
||||
.forResource("Patient")
|
||||
.where(Patient.NAME.matches().value("hello"))
|
||||
.and(Patient.NAME.matches().value("goodbye"))
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
|
||||
assertEquals("findStringAndListParam", myLastMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringAndListNotSelectedWhenSingleParameterProvided() {
|
||||
ourServerRule.getRestfulServer().registerProviders(new StringStrengthsWithOptionalResourceProvider());
|
||||
|
||||
myClient
|
||||
.search()
|
||||
.forResource("Patient")
|
||||
.where(Patient.NAME.matches().value("hello"))
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
|
||||
assertEquals("findString", myLastMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyStringSearchProvidedWithNoParameters() {
|
||||
ourServerRule.getRestfulServer().registerProviders(new StringStrengthsWithOptionalResourceProvider());
|
||||
|
||||
myClient
|
||||
.search()
|
||||
.forResource("Patient")
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
|
||||
assertEquals("find", myLastMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyStringSearchProvidedWithNoParameters2() {
|
||||
ourServerRule.getRestfulServer().registerProviders(new StringStrengthsWithOptionalResourceProviderReverseOrder());
|
||||
|
||||
myClient
|
||||
.search()
|
||||
.forResource("Patient")
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
|
||||
assertEquals("find", myLastMethod);
|
||||
}
|
||||
|
||||
public class DateStrengthsWithRequiredResourceProvider implements IResourceProvider {
|
||||
|
||||
@Override
|
||||
public Class<Patient> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
|
||||
@Search
|
||||
public List<Patient> find() {
|
||||
myLastMethod = "find";
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
|
||||
@Search()
|
||||
public List<Patient> findDateParam(
|
||||
@RequiredParam(name = Patient.SP_BIRTHDATE) DateParam theDate) {
|
||||
myLastMethod = "findDateParam";
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
|
||||
@Search()
|
||||
public List<Patient> findDateRangeParam(
|
||||
@RequiredParam(name = Patient.SP_BIRTHDATE) DateRangeParam theRange) {
|
||||
myLastMethod = "findDateRangeParam";
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class StringStrengthsWithOptionalResourceProvider implements IResourceProvider {
|
||||
|
||||
@Override
|
||||
public Class<Patient> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
|
||||
@Search()
|
||||
public List<Patient> findString(
|
||||
@OptionalParam(name = Patient.SP_NAME) String theDate) {
|
||||
myLastMethod = "findString";
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
|
||||
@Search()
|
||||
public List<Patient> findStringAndListParam(
|
||||
@OptionalParam(name = Patient.SP_NAME) StringAndListParam theRange) {
|
||||
myLastMethod = "findStringAndListParam";
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
|
||||
@Search
|
||||
public List<Patient> find() {
|
||||
myLastMethod = "find";
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class StringStrengthsWithOptionalResourceProviderReverseOrder implements IResourceProvider {
|
||||
|
||||
@Override
|
||||
public Class<Patient> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
|
||||
@Search()
|
||||
public List<Patient> findA(
|
||||
@OptionalParam(name = Patient.SP_NAME) String theDate) {
|
||||
myLastMethod = "findString";
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
|
||||
@Search
|
||||
public List<Patient> findB() {
|
||||
myLastMethod = "find";
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -21,10 +21,12 @@ package ca.uhn.fhir.test.utilities.server;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
|
@ -43,35 +45,66 @@ import java.util.concurrent.TimeUnit;
|
|||
public class RestfulServerRule implements TestRule {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(RestfulServerRule.class);
|
||||
|
||||
private final FhirContext myFhirContext;
|
||||
private final Object[] myProviders;
|
||||
private FhirContext myFhirContext;
|
||||
private Object[] myProviders;
|
||||
private FhirVersionEnum myFhirVersion;
|
||||
private Server myServer;
|
||||
private RestfulServer myServlet;
|
||||
private int myPort;
|
||||
private CloseableHttpClient myHttpClient;
|
||||
private IGenericClient myFhirClient;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public RestfulServerRule(FhirContext theFhirContext, Object... theProviders) {
|
||||
Validate.notNull(theFhirContext);
|
||||
myFhirContext = theFhirContext;
|
||||
myProviders = theProviders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor: If this is used, it will create and tear down a FhirContext which is good for memory
|
||||
*/
|
||||
public RestfulServerRule(FhirVersionEnum theFhirVersionEnum) {
|
||||
Validate.notNull(theFhirVersionEnum);
|
||||
myFhirVersion = theFhirVersionEnum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement apply(Statement theBase, Description theDescription) {
|
||||
return new Statement() {
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
createContextIfNeeded();
|
||||
startServer();
|
||||
theBase.evaluate();
|
||||
stopServer();
|
||||
destroyContextIfWeCreatedIt();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void createContextIfNeeded() {
|
||||
if (myFhirVersion != null) {
|
||||
myFhirContext = new FhirContext(myFhirVersion);
|
||||
}
|
||||
}
|
||||
|
||||
private void destroyContextIfWeCreatedIt() {
|
||||
if (myFhirVersion != null) {
|
||||
myFhirContext = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void stopServer() throws Exception {
|
||||
JettyUtil.closeServer(myServer);
|
||||
myServer = null;
|
||||
myFhirClient = null;
|
||||
|
||||
myHttpClient.close();
|
||||
myHttpClient = null;
|
||||
}
|
||||
|
||||
private void startServer() throws Exception {
|
||||
|
@ -80,7 +113,9 @@ public class RestfulServerRule implements TestRule {
|
|||
ServletHandler servletHandler = new ServletHandler();
|
||||
myServlet = new RestfulServer(myFhirContext);
|
||||
myServlet.setDefaultPrettyPrint(true);
|
||||
myServlet.registerProviders(myProviders);
|
||||
if (myProviders != null) {
|
||||
myServlet.registerProviders(myProviders);
|
||||
}
|
||||
ServletHolder servletHolder = new ServletHolder(myServlet);
|
||||
servletHandler.addServletWithMapping(servletHolder, "/*");
|
||||
|
||||
|
|
Loading…
Reference in New Issue