Merge remote-tracking branch 'remotes/origin/master' into im_20200316_lastn_operation_elasticsearch
This commit is contained in:
commit
540375a7eb
|
@ -58,6 +58,14 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
@SuppressWarnings("WeakerAccess")
|
||||
public abstract class BaseParser implements IParser {
|
||||
|
||||
/**
|
||||
* Any resources that were created by the parser (i.e. by parsing a serialized resource) will have
|
||||
* a {@link IBaseResource#getUserData(String) user data} property with this key.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public static final String RESOURCE_CREATED_BY_PARSER = BaseParser.class.getName() + "_" + "RESOURCE_CREATED_BY_PARSER";
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseParser.class);
|
||||
|
||||
private static final Set<String> notEncodeForContainedResource = new HashSet<>(Arrays.asList("security", "versionId", "lastUpdated"));
|
||||
|
|
|
@ -43,6 +43,7 @@ import ca.uhn.fhir.util.ReflectionUtil;
|
|||
import ca.uhn.fhir.util.XmlUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
|
||||
|
@ -456,7 +457,7 @@ class ParserState<T> {
|
|||
RuntimePrimitiveDatatypeDefinition primitiveTarget = (RuntimePrimitiveDatatypeDefinition) target;
|
||||
IPrimitiveType<?> newChildInstance = newPrimitiveInstance(myDefinition, primitiveTarget);
|
||||
myDefinition.getMutator().addValue(myParentInstance, newChildInstance);
|
||||
PrimitiveState newState = new PrimitiveState(getPreResourceState(), newChildInstance, theLocalPart);
|
||||
PrimitiveState newState = new PrimitiveState(getPreResourceState(), newChildInstance, theLocalPart, primitiveTarget.getName());
|
||||
push(newState);
|
||||
return;
|
||||
}
|
||||
|
@ -495,10 +496,10 @@ class ParserState<T> {
|
|||
|
||||
private class ElementCompositeState extends BaseState {
|
||||
|
||||
private BaseRuntimeElementCompositeDefinition<?> myDefinition;
|
||||
private IBase myInstance;
|
||||
private Set<String> myParsedNonRepeatableNames = new HashSet<>();
|
||||
private String myElementName;
|
||||
private final BaseRuntimeElementCompositeDefinition<?> myDefinition;
|
||||
private final IBase myInstance;
|
||||
private final Set<String> myParsedNonRepeatableNames = new HashSet<>();
|
||||
private final String myElementName;
|
||||
|
||||
ElementCompositeState(PreResourceState thePreResourceState, String theElementName, BaseRuntimeElementCompositeDefinition<?> theDef, IBase theInstance) {
|
||||
super(thePreResourceState);
|
||||
|
@ -585,7 +586,7 @@ class ParserState<T> {
|
|||
IPrimitiveType<?> newChildInstance;
|
||||
newChildInstance = getPrimitiveInstance(child, primitiveTarget, theChildName);
|
||||
child.getMutator().addValue(myInstance, newChildInstance);
|
||||
PrimitiveState newState = new PrimitiveState(getPreResourceState(), newChildInstance, theChildName);
|
||||
PrimitiveState newState = new PrimitiveState(getPreResourceState(), newChildInstance, theChildName, primitiveTarget.getName());
|
||||
push(newState);
|
||||
return;
|
||||
}
|
||||
|
@ -668,7 +669,7 @@ class ParserState<T> {
|
|||
|
||||
public class ElementIdState extends BaseState {
|
||||
|
||||
private IBaseElement myElement;
|
||||
private final IBaseElement myElement;
|
||||
|
||||
ElementIdState(ParserState<T>.PreResourceState thePreResourceState, IBaseElement theElement) {
|
||||
super(thePreResourceState);
|
||||
|
@ -689,7 +690,7 @@ class ParserState<T> {
|
|||
|
||||
private class ExtensionState extends BaseState {
|
||||
|
||||
private IBaseExtension<?, ?> myExtension;
|
||||
private final IBaseExtension<?, ?> myExtension;
|
||||
|
||||
ExtensionState(PreResourceState thePreResourceState, IBaseExtension<?, ?> theExtension) {
|
||||
super(thePreResourceState);
|
||||
|
@ -752,7 +753,7 @@ class ParserState<T> {
|
|||
RuntimePrimitiveDatatypeDefinition primitiveTarget = (RuntimePrimitiveDatatypeDefinition) target;
|
||||
IPrimitiveType<?> newChildInstance = newInstance(primitiveTarget);
|
||||
myExtension.setValue(newChildInstance);
|
||||
PrimitiveState newState = new PrimitiveState(getPreResourceState(), newChildInstance, theLocalPart);
|
||||
PrimitiveState newState = new PrimitiveState(getPreResourceState(), newChildInstance, theLocalPart, primitiveTarget.getName());
|
||||
push(newState);
|
||||
return;
|
||||
}
|
||||
|
@ -782,7 +783,7 @@ class ParserState<T> {
|
|||
|
||||
public class IdentifiableElementIdState extends BaseState {
|
||||
|
||||
private IIdentifiableElement myElement;
|
||||
private final IIdentifiableElement myElement;
|
||||
|
||||
public IdentifiableElementIdState(ParserState<T>.PreResourceState thePreResourceState, IIdentifiableElement theElement) {
|
||||
super(thePreResourceState);
|
||||
|
@ -802,7 +803,7 @@ class ParserState<T> {
|
|||
}
|
||||
|
||||
private class MetaElementState extends BaseState {
|
||||
private ResourceMetadataMap myMap;
|
||||
private final ResourceMetadataMap myMap;
|
||||
|
||||
public MetaElementState(ParserState<T>.PreResourceState thePreResourceState, ResourceMetadataMap theMap) {
|
||||
super(thePreResourceState);
|
||||
|
@ -824,7 +825,7 @@ class ParserState<T> {
|
|||
break;
|
||||
case "lastUpdated":
|
||||
InstantDt updated = new InstantDt();
|
||||
push(new PrimitiveState(getPreResourceState(), updated, theLocalPart));
|
||||
push(new PrimitiveState(getPreResourceState(), updated, theLocalPart, "instant"));
|
||||
myMap.put(ResourceMetadataKeyEnum.UPDATED, updated);
|
||||
break;
|
||||
case "security":
|
||||
|
@ -850,7 +851,7 @@ class ParserState<T> {
|
|||
newProfiles = new ArrayList<>(1);
|
||||
}
|
||||
IdDt profile = new IdDt();
|
||||
push(new PrimitiveState(getPreResourceState(), profile, theLocalPart));
|
||||
push(new PrimitiveState(getPreResourceState(), profile, theLocalPart, "id"));
|
||||
newProfiles.add(profile);
|
||||
myMap.put(ResourceMetadataKeyEnum.PROFILES, Collections.unmodifiableList(newProfiles));
|
||||
break;
|
||||
|
@ -1048,6 +1049,8 @@ class ParserState<T> {
|
|||
}
|
||||
}
|
||||
|
||||
myInstance.setUserData(BaseParser.RESOURCE_CREATED_BY_PARSER, Boolean.TRUE);
|
||||
|
||||
populateTarget();
|
||||
}
|
||||
|
||||
|
@ -1269,41 +1272,50 @@ class ParserState<T> {
|
|||
|
||||
private class PrimitiveState extends BaseState {
|
||||
private final String myChildName;
|
||||
private final String myTypeName;
|
||||
private IPrimitiveType<?> myInstance;
|
||||
|
||||
PrimitiveState(PreResourceState thePreResourceState, IPrimitiveType<?> theInstance, String theChildName) {
|
||||
PrimitiveState(PreResourceState thePreResourceState, IPrimitiveType<?> theInstance, String theChildName, String theTypeName) {
|
||||
super(thePreResourceState);
|
||||
myInstance = theInstance;
|
||||
myChildName = theChildName;
|
||||
myTypeName = theTypeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attributeValue(String theName, String theValue) throws DataFormatException {
|
||||
String value = theValue;
|
||||
if ("value".equals(theName)) {
|
||||
if ("".equals(theValue)) {
|
||||
if ("".equals(value)) {
|
||||
ParseLocation location = ParseLocation.fromElementName(myChildName);
|
||||
myErrorHandler.invalidValue(location, theValue, "Attribute value must not be empty (\"\")");
|
||||
myErrorHandler.invalidValue(location, value, "Attribute value must not be empty (\"\")");
|
||||
} else {
|
||||
if ("decimal".equals(myTypeName)) {
|
||||
if (value != null && value.startsWith(".") && NumberUtils.isDigits(value.substring(1))) {
|
||||
value = "0" + value;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
myInstance.setValueAsString(theValue);
|
||||
myInstance.setValueAsString(value);
|
||||
} catch (DataFormatException | IllegalArgumentException e) {
|
||||
ParseLocation location = ParseLocation.fromElementName(myChildName);
|
||||
myErrorHandler.invalidValue(location, theValue, e.getMessage());
|
||||
myErrorHandler.invalidValue(location, value, e.getMessage());
|
||||
}
|
||||
}
|
||||
} else if ("id".equals(theName)) {
|
||||
if (myInstance instanceof IIdentifiableElement) {
|
||||
((IIdentifiableElement) myInstance).setElementSpecificId(theValue);
|
||||
((IIdentifiableElement) myInstance).setElementSpecificId(value);
|
||||
} else if (myInstance instanceof IBaseElement) {
|
||||
((IBaseElement) myInstance).setId(theValue);
|
||||
((IBaseElement) myInstance).setId(value);
|
||||
} else if (myInstance instanceof IBaseResource) {
|
||||
new IdDt(theValue).applyTo((org.hl7.fhir.instance.model.api.IBaseResource) myInstance);
|
||||
new IdDt(value).applyTo((org.hl7.fhir.instance.model.api.IBaseResource) myInstance);
|
||||
} else {
|
||||
ParseLocation location = ParseLocation.fromElementName(myChildName);
|
||||
myErrorHandler.unknownAttribute(location, theName);
|
||||
}
|
||||
} else {
|
||||
super.attributeValue(theName, theValue);
|
||||
super.attributeValue(theName, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1348,7 +1360,7 @@ class ParserState<T> {
|
|||
@Override
|
||||
public void enteringNewElement(String theNamespace, String theChildName) throws DataFormatException {
|
||||
if ("id".equals(theChildName)) {
|
||||
push(new PrimitiveState(getPreResourceState(), myInstance.getId(), theChildName));
|
||||
push(new PrimitiveState(getPreResourceState(), myInstance.getId(), theChildName, "id"));
|
||||
} else if ("meta".equals(theChildName)) {
|
||||
push(new MetaElementState(getPreResourceState(), myInstance.getResourceMetadata()));
|
||||
} else {
|
||||
|
|
|
@ -35,7 +35,6 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
|
|||
import com.fasterxml.jackson.databind.node.DecimalNode;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.apache.jena.tdb.setup.BuilderStdDB;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PushbackReader;
|
||||
|
|
|
@ -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,7 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 1801
|
||||
title: "When invoking JPA DAO methods programatically to store a resource (as opposed to using the FHIR REST API), if
|
||||
the resource being stored had any contained resources, these would sometimes not be visible to the search parameter
|
||||
indexer, leading to missing search params. This is a very fringe use case, but a workaround has been put in place to
|
||||
solve it."
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 1801
|
||||
title: "In previous versions of HAPI FHIR, the server incorrectly silently accepted decimal numbers in JSON with no
|
||||
leading numbers (e.g. `.123`). These will now be rejected by the JSON parser. Any values with this string that
|
||||
have previously been stored in the JPA server database will now automatically convert the value to `0.123`."
|
|
@ -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."
|
|
@ -11,6 +11,7 @@ import ca.uhn.fhir.jpa.bulk.BulkDataExportProvider;
|
|||
import ca.uhn.fhir.jpa.bulk.BulkDataExportSvcImpl;
|
||||
import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc;
|
||||
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
||||
import ca.uhn.fhir.jpa.dao.index.DaoResourceLinkResolver;
|
||||
import ca.uhn.fhir.jpa.entity.Search;
|
||||
import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
|
||||
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
|
||||
|
@ -33,6 +34,7 @@ import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc;
|
|||
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
|
||||
import ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl;
|
||||
import ca.uhn.fhir.jpa.searchparam.config.SearchParamConfig;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
|
@ -46,6 +48,7 @@ import org.springframework.context.annotation.Configuration;
|
|||
import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
|
@ -158,6 +161,12 @@ public abstract class BaseConfig {
|
|||
return new BinaryStorageInterceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public IResourceLinkResolver daoResourceLinkResolver() {
|
||||
return new DaoResourceLinkResolver();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ISearchCacheSvc searchCacheSvc() {
|
||||
return new DatabaseSearchCacheSvcImpl();
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
package ca.uhn.fhir.jpa.dao;
|
||||
|
||||
import ca.uhn.fhir.context.*;
|
||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
||||
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
||||
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.context.RuntimeChildResourceDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
|
@ -8,7 +15,12 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
|||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.api.dao.IDao;
|
||||
import ca.uhn.fhir.jpa.api.dao.IJpaDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.*;
|
||||
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
|
||||
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceProvenanceDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceTagDao;
|
||||
import ca.uhn.fhir.jpa.dao.expunge.ExpungeService;
|
||||
import ca.uhn.fhir.jpa.dao.index.DaoSearchParamSynchronizer;
|
||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||
|
@ -22,7 +34,6 @@ import ca.uhn.fhir.jpa.model.entity.*;
|
|||
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
|
||||
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
|
||||
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProviderFactory;
|
||||
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams;
|
||||
|
@ -78,7 +89,11 @@ import org.springframework.transaction.support.TransactionSynchronizationAdapter
|
|||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.persistence.*;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import javax.persistence.PersistenceContextType;
|
||||
import javax.persistence.TypedQuery;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
@ -87,7 +102,12 @@ import javax.xml.stream.events.XMLEvent;
|
|||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.*;
|
||||
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.left;
|
||||
import static org.apache.commons.lang3.StringUtils.trim;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
|
@ -850,8 +870,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
// 4. parse the text to FHIR
|
||||
R retVal;
|
||||
if (resourceEncoding != ResourceEncodingEnum.DEL) {
|
||||
IParser parser = resourceEncoding.newParser(getContext(theEntity.getFhirVersion()));
|
||||
parser.setParserErrorHandler(new LenientErrorHandler(false).setErrorOnInvalidValue(false));
|
||||
|
||||
LenientErrorHandler errorHandler = new LenientErrorHandler(false).setErrorOnInvalidValue(false);
|
||||
IParser parser = new TolerantJsonParser(getContext(theEntity.getFhirVersion()), errorHandler);
|
||||
|
||||
try {
|
||||
retVal = parser.parseResource(resourceType, resourceText);
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
package ca.uhn.fhir.jpa.dao;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2020 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.parser.IParserErrorHandler;
|
||||
import ca.uhn.fhir.parser.JsonParser;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
|
||||
class TolerantJsonParser extends JsonParser {
|
||||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(TolerantJsonParser.class);
|
||||
|
||||
TolerantJsonParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
|
||||
super(theContext, theParserErrorHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends IBaseResource> T parseResource(Class<T> theResourceType, String theMessageString) {
|
||||
try {
|
||||
return super.parseResource(theResourceType, theMessageString);
|
||||
} catch (DataFormatException e) {
|
||||
if (defaultString(e.getMessage()).contains("Unexpected character ('.' (code 46))")) {
|
||||
|
||||
/*
|
||||
* The following is a hacky and gross workaround until the following PR is hopefully merged:
|
||||
* https://github.com/FasterXML/jackson-core/pull/611
|
||||
*
|
||||
* The issue this solves is that under Gson it was possible to store JSON containing
|
||||
* decimal numbers with no leading integer, e.g. .123
|
||||
*
|
||||
* These don't parse in Jackson, meaning we can be stuck with data in the database
|
||||
* that can't be loaded back out.
|
||||
*
|
||||
* Note that if we fix this in the future to rely on Jackson natively handing this
|
||||
* nicely we may or may not be able to remove some code from
|
||||
* ParserState.Primitive state too.
|
||||
*/
|
||||
|
||||
Gson gson = new Gson();
|
||||
|
||||
JsonObject object = gson.fromJson(theMessageString, JsonObject.class);
|
||||
String corrected = gson.toJson(object);
|
||||
|
||||
return super.parseResource(theResourceType, corrected);
|
||||
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -49,7 +49,6 @@ import javax.persistence.PersistenceContext;
|
|||
import javax.persistence.PersistenceContextType;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class DaoResourceLinkResolver implements IResourceLinkResolver {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DaoResourceLinkResolver.class);
|
||||
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
||||
|
|
|
@ -23,8 +23,8 @@ package ca.uhn.fhir.jpa.dao.index;
|
|||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
||||
import ca.uhn.fhir.jpa.dao.MatchResourceUrlService;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao;
|
||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
||||
|
@ -34,7 +34,6 @@ import ca.uhn.fhir.jpa.model.entity.ResourceLink;
|
|||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceLinkExtractor;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
|
@ -84,8 +83,6 @@ public class SearchParamWithInlineReferencesExtractor {
|
|||
@Autowired
|
||||
private SearchParamExtractorService mySearchParamExtractorService;
|
||||
@Autowired
|
||||
private ResourceLinkExtractor myResourceLinkExtractor;
|
||||
@Autowired
|
||||
private DaoResourceLinkResolver myDaoResourceLinkResolver;
|
||||
@Autowired
|
||||
private DaoSearchParamSynchronizer myDaoSearchParamSynchronizer;
|
||||
|
@ -96,19 +93,15 @@ public class SearchParamWithInlineReferencesExtractor {
|
|||
protected EntityManager myEntityManager;
|
||||
|
||||
public void populateFromResource(ResourceIndexedSearchParams theParams, Date theUpdateTime, ResourceTable theEntity, IBaseResource theResource, ResourceIndexedSearchParams theExistingParams, RequestDetails theRequest) {
|
||||
mySearchParamExtractorService.extractFromResource(theRequest, theParams, theEntity, theResource);
|
||||
extractInlineReferences(theResource, theRequest);
|
||||
|
||||
mySearchParamExtractorService.extractFromResource(theRequest, theParams, theEntity, theResource, theUpdateTime, true);
|
||||
|
||||
Set<Map.Entry<String, RuntimeSearchParam>> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).entrySet();
|
||||
if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.ENABLED) {
|
||||
theParams.findMissingSearchParams(myDaoConfig.getModelConfig(), theEntity, activeSearchParams);
|
||||
}
|
||||
|
||||
theParams.setUpdatedTime(theUpdateTime);
|
||||
|
||||
extractInlineReferences(theResource, theRequest);
|
||||
|
||||
myResourceLinkExtractor.extractResourceLinks(theParams, theEntity, theResource, theUpdateTime, myDaoResourceLinkResolver, true, theRequest);
|
||||
|
||||
/*
|
||||
* If the existing resource already has links and those match links we still want, use them instead of removing them and re adding them
|
||||
*/
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
package ca.uhn.fhir.jpa.util.xmlpatch;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import com.github.dnault.xmlpatch.Patcher;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import com.github.dnault.xmlpatch.Patcher;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
|
@ -28,9 +30,6 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
|
||||
public class XmlPatchUtils {
|
||||
|
||||
public static <T extends IBaseResource> T apply(FhirContext theCtx, T theResourceToUpdate, String thePatchBody) {
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
package ca.uhn.fhir.jpa.dao;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.parser.LenientErrorHandler;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TolerantJsonParserR4Test {
|
||||
|
||||
private FhirContext myFhirContext = FhirContext.forR4();
|
||||
|
||||
@Test
|
||||
public void testParseInvalidNumeric() {
|
||||
String input = "{\n" +
|
||||
"\"resourceType\": \"Observation\",\n" +
|
||||
"\"valueQuantity\": {\n" +
|
||||
" \"value\": .5\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
|
||||
|
||||
TolerantJsonParser parser = new TolerantJsonParser(myFhirContext, new LenientErrorHandler());
|
||||
Observation obs = parser.parseResource(Observation.class, input);
|
||||
|
||||
assertEquals("0.5", obs.getValueQuantity().getValueElement().getValueAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseInvalidNumeric2() {
|
||||
String input = "{\n" +
|
||||
"\"resourceType\": \"Observation\",\n" +
|
||||
"\"valueQuantity\": {\n" +
|
||||
" \"value\": .\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
|
||||
|
||||
TolerantJsonParser parser = new TolerantJsonParser(myFhirContext, new LenientErrorHandler());
|
||||
try {
|
||||
parser.parseResource(Observation.class, input);
|
||||
} catch (DataFormatException e) {
|
||||
assertThat(e.getMessage(), containsString("[element=\"value\"] Invalid attribute value \".\""));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -218,6 +218,8 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
|
|||
@Autowired
|
||||
protected IResourceIndexedSearchParamStringDao myResourceIndexedSearchParamStringDao;
|
||||
@Autowired
|
||||
protected IResourceIndexedSearchParamTokenDao myResourceIndexedSearchParamTokenDao;
|
||||
@Autowired
|
||||
protected IResourceTableDao myResourceTableDao;
|
||||
@Autowired
|
||||
protected IResourceTagDao myResourceTagDao;
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package ca.uhn.fhir.jpa.dao.dstu3;
|
||||
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.parser.IParser;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.*;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
|
@ -20,6 +22,7 @@ import org.junit.Before;
|
|||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
@ -1054,6 +1057,48 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProgramaticallyContainedByReferenceAreStillResolvable() {
|
||||
SearchParameter sp = new SearchParameter();
|
||||
sp.setUrl("http://hapifhir.io/fhir/StructureDefinition/sp-unique");
|
||||
sp.setName("MEDICATIONADMINISTRATION-INGREDIENT-MEDICATION");
|
||||
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
||||
sp.setCode("medicationadministration-ingredient-medication");
|
||||
sp.addBase("MedicationAdministration");
|
||||
sp.setType(Enumerations.SearchParamType.TOKEN);
|
||||
sp.setExpression("MedicationAdministration.medication.resolve().ingredient.item.as(Reference).resolve().code");
|
||||
mySearchParameterDao.create(sp);
|
||||
mySearchParamRegistry.forceRefresh();
|
||||
|
||||
Medication ingredient = new Medication();
|
||||
ingredient.getCode().addCoding().setSystem("system").setCode("code");
|
||||
|
||||
Medication medication = new Medication();
|
||||
medication.addIngredient().setItem(new Reference(ingredient));
|
||||
|
||||
MedicationAdministration medAdmin = new MedicationAdministration();
|
||||
medAdmin.setMedication(new Reference(medication));
|
||||
|
||||
myMedicationAdministrationDao.create(medAdmin);
|
||||
|
||||
runInTransaction(()->{
|
||||
List<ResourceIndexedSearchParamToken> tokens = myResourceIndexedSearchParamTokenDao
|
||||
.findAll()
|
||||
.stream()
|
||||
.filter(t -> t.getParamName().equals("medicationadministration-ingredient-medication"))
|
||||
.collect(Collectors.toList());
|
||||
ourLog.info("Tokens: {}", tokens);
|
||||
assertEquals(tokens.toString(), 1, tokens.size());
|
||||
|
||||
});
|
||||
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.add("medicationadministration-ingredient-medication", new TokenParam("system","code"));
|
||||
assertEquals(1, myMedicationAdministrationDao.search(map).size().intValue());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() {
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -253,6 +253,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
|
|||
|
||||
MessageHeader messageHeader = new MessageHeader();
|
||||
messageHeader.setId("123");
|
||||
messageHeader.setDefinition("Hello");
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.setType(Bundle.BundleType.MESSAGE);
|
||||
bundle.addEntry()
|
||||
|
|
|
@ -22,9 +22,7 @@ package ca.uhn.fhir.jpa.searchparam.config;
|
|||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceLinkExtractor;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu2;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu3;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4;
|
||||
|
@ -32,13 +30,11 @@ import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR5;
|
|||
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService;
|
||||
import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryResourceMatcher;
|
||||
import ca.uhn.fhir.jpa.searchparam.matcher.IndexedSearchParamExtractor;
|
||||
import ca.uhn.fhir.jpa.searchparam.matcher.InlineResourceLinkResolver;
|
||||
import ca.uhn.fhir.jpa.searchparam.matcher.SearchParamMatcher;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
@ -79,11 +75,6 @@ public class SearchParamConfig {
|
|||
return new MatchUrlService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ResourceLinkExtractor resourceLinkExtractor() {
|
||||
return new ResourceLinkExtractor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
public SearchParamExtractorService searchParamExtractorService(){
|
||||
|
@ -95,11 +86,6 @@ public class SearchParamConfig {
|
|||
return new IndexedSearchParamExtractor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public InlineResourceLinkResolver inlineResourceLinkResolver() {
|
||||
return new InlineResourceLinkResolver();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public InMemoryResourceMatcher InMemoryResourceMatcher() {
|
||||
return new InMemoryResourceMatcher();
|
||||
|
|
|
@ -1015,6 +1015,10 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
systemAsString = myUseSystem;
|
||||
}
|
||||
|
||||
if (value instanceof IIdType) {
|
||||
valueAsString = ((IIdType) value).getIdPart();
|
||||
}
|
||||
|
||||
BaseSearchParamExtractor.this.createTokenIndexIfNotBlank(myResourceTypeName, params, searchParam, systemAsString, valueAsString);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -26,18 +26,22 @@ import ca.uhn.fhir.model.api.IQueryParameterType;
|
|||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.compare;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public final class ResourceIndexedSearchParams {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceIndexedSearchParams.class);
|
||||
|
||||
final public Collection<ResourceIndexedSearchParamString> myStringParams = new ArrayList<>();
|
||||
final public Collection<ResourceIndexedSearchParamToken> myTokenParams = new HashSet<>();
|
||||
final public Collection<ResourceIndexedSearchParamNumber> myNumberParams = new ArrayList<>();
|
||||
|
@ -110,7 +114,7 @@ public final class ResourceIndexedSearchParams {
|
|||
theEntity.setHasLinks(myLinks.isEmpty() == false);
|
||||
}
|
||||
|
||||
public void setUpdatedTime(Date theUpdateTime) {
|
||||
void setUpdatedTime(Date theUpdateTime) {
|
||||
setUpdatedTime(myStringParams, theUpdateTime);
|
||||
setUpdatedTime(myNumberParams, theUpdateTime);
|
||||
setUpdatedTime(myQuantityParams, theUpdateTime);
|
||||
|
|
|
@ -1,192 +0,0 @@
|
|||
package ca.uhn.fhir.jpa.searchparam.extractor;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Search Parameters
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2020 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public class ResourceLinkExtractor {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceLinkExtractor.class);
|
||||
|
||||
@Autowired
|
||||
private ModelConfig myModelConfig;
|
||||
@Autowired
|
||||
private FhirContext myContext;
|
||||
@Autowired
|
||||
private ISearchParamRegistry mySearchParamRegistry;
|
||||
@Autowired
|
||||
private ISearchParamExtractor mySearchParamExtractor;
|
||||
@Autowired
|
||||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||
|
||||
public void extractResourceLinks(ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver, boolean theFailOnInvalidReference, RequestDetails theRequest) {
|
||||
String resourceName = myContext.getResourceDefinition(theResource).getName();
|
||||
|
||||
ISearchParamExtractor.SearchParamSet<PathAndRef> refs = mySearchParamExtractor.extractResourceLinks(theResource);
|
||||
SearchParamExtractorService.handleWarnings(theRequest, myInterceptorBroadcaster, refs);
|
||||
|
||||
Map<String, IResourceLookup> resourceIdToResolvedTarget = new HashMap<>();
|
||||
for (PathAndRef nextPathAndRef : refs) {
|
||||
RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(resourceName, nextPathAndRef.getSearchParamName());
|
||||
extractResourceLinks(theParams, theEntity, theUpdateTime, theResourceLinkResolver, searchParam, nextPathAndRef, theFailOnInvalidReference, theRequest, resourceIdToResolvedTarget);
|
||||
}
|
||||
|
||||
theEntity.setHasLinks(theParams.myLinks.size() > 0);
|
||||
}
|
||||
|
||||
private void extractResourceLinks(ResourceIndexedSearchParams theParams, ResourceTable theEntity, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver, RuntimeSearchParam theRuntimeSearchParam, PathAndRef thePathAndRef, boolean theFailOnInvalidReference, RequestDetails theRequest, Map<String, IResourceLookup> theResourceIdToResolvedTarget) {
|
||||
IBaseReference nextReference = thePathAndRef.getRef();
|
||||
IIdType nextId = nextReference.getReferenceElement();
|
||||
String path = thePathAndRef.getPath();
|
||||
|
||||
/*
|
||||
* This can only really happen if the DAO is being called
|
||||
* programmatically with a Bundle (not through the FHIR REST API)
|
||||
* but Smile does this
|
||||
*/
|
||||
if (nextId.isEmpty() && nextReference.getResource() != null) {
|
||||
nextId = nextReference.getResource().getIdElement();
|
||||
}
|
||||
|
||||
theParams.myPopulatedResourceLinkParameters.add(thePathAndRef.getSearchParamName());
|
||||
|
||||
boolean canonical = thePathAndRef.isCanonical();
|
||||
if (LogicalReferenceHelper.isLogicalReference(myModelConfig, nextId) || canonical) {
|
||||
String value = nextId.getValue();
|
||||
ResourceLink resourceLink = ResourceLink.forLogicalReference(thePathAndRef.getPath(), theEntity, value, theUpdateTime);
|
||||
if (theParams.myLinks.add(resourceLink)) {
|
||||
ourLog.debug("Indexing remote resource reference URL: {}", nextId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String baseUrl = nextId.getBaseUrl();
|
||||
String typeString = nextId.getResourceType();
|
||||
if (isBlank(typeString)) {
|
||||
String msg = "Invalid resource reference found at path[" + path + "] - Does not contain resource type - " + nextId.getValue();
|
||||
if (theFailOnInvalidReference) {
|
||||
throw new InvalidRequestException(msg);
|
||||
} else {
|
||||
ourLog.debug(msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
RuntimeResourceDefinition resourceDefinition;
|
||||
try {
|
||||
resourceDefinition = myContext.getResourceDefinition(typeString);
|
||||
} catch (DataFormatException e) {
|
||||
String msg = "Invalid resource reference found at path[" + path + "] - Resource type is unknown or not supported on this server - " + nextId.getValue();
|
||||
if (theFailOnInvalidReference) {
|
||||
throw new InvalidRequestException(msg);
|
||||
} else {
|
||||
ourLog.debug(msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (theRuntimeSearchParam.hasTargets()) {
|
||||
if (!theRuntimeSearchParam.getTargets().contains(typeString)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (isNotBlank(baseUrl)) {
|
||||
if (!myModelConfig.getTreatBaseUrlsAsLocal().contains(baseUrl) && !myModelConfig.isAllowExternalReferences()) {
|
||||
String msg = myContext.getLocalizer().getMessage(BaseSearchParamExtractor.class, "externalReferenceNotAllowed", nextId.getValue());
|
||||
throw new InvalidRequestException(msg);
|
||||
} else {
|
||||
ResourceLink resourceLink = ResourceLink.forAbsoluteReference(thePathAndRef.getPath(), theEntity, nextId, theUpdateTime);
|
||||
if (theParams.myLinks.add(resourceLink)) {
|
||||
ourLog.debug("Indexing remote resource reference URL: {}", nextId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Class<? extends IBaseResource> type = resourceDefinition.getImplementingClass();
|
||||
String id = nextId.getIdPart();
|
||||
if (StringUtils.isBlank(id)) {
|
||||
String msg = "Invalid resource reference found at path[" + path + "] - Does not contain resource ID - " + nextId.getValue();
|
||||
if (theFailOnInvalidReference) {
|
||||
throw new InvalidRequestException(msg);
|
||||
} else {
|
||||
ourLog.debug(msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
theResourceLinkResolver.validateTypeOrThrowException(type);
|
||||
ResourceLink resourceLink = createResourceLink(theEntity, theUpdateTime, theResourceLinkResolver, theRuntimeSearchParam, path, thePathAndRef, nextId, typeString, type, nextReference, theRequest, theResourceIdToResolvedTarget);
|
||||
if (resourceLink == null) {
|
||||
return;
|
||||
}
|
||||
theParams.myLinks.add(resourceLink);
|
||||
}
|
||||
|
||||
private ResourceLink createResourceLink(ResourceTable theEntity, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver, RuntimeSearchParam nextSpDef, String theNextPathsUnsplit, PathAndRef nextPathAndRef, IIdType theNextId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest, Map<String, IResourceLookup> theResourceIdToResolvedTarget) {
|
||||
/*
|
||||
* We keep a cache of resolved target resources. This is good since for some resource types, there
|
||||
* are multiple search parameters that map to the same element path within a resource (e.g.
|
||||
* Observation:patient and Observation.subject and we don't want to force a resolution of the
|
||||
* target any more times than we have to.
|
||||
*/
|
||||
|
||||
IResourceLookup targetResource = theResourceIdToResolvedTarget.get(theNextId.getValue());
|
||||
if (targetResource == null) {
|
||||
targetResource = theResourceLinkResolver.findTargetResource(nextSpDef, theNextPathsUnsplit, theNextId, theTypeString, theType, theReference, theRequest);
|
||||
}
|
||||
|
||||
if (targetResource == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
theResourceIdToResolvedTarget.put(theNextId.getValue(), targetResource);
|
||||
|
||||
String targetResourceType = targetResource.getResourceType();
|
||||
Long targetResourcePid = targetResource.getResourceId();
|
||||
String targetResourceIdPart = theNextId.getIdPart();
|
||||
return ResourceLink.forLocalReference(nextPathAndRef.getPath(), theEntity, targetResourceType, targetResourcePid, targetResourceIdPart, theUpdateTime);
|
||||
}
|
||||
|
||||
}
|
|
@ -20,21 +20,36 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
||||
import ca.uhn.fhir.jpa.model.entity.*;
|
||||
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.parser.IParser;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public class SearchParamExtractorService {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParamExtractorService.class);
|
||||
|
@ -43,30 +58,60 @@ public class SearchParamExtractorService {
|
|||
private ISearchParamExtractor mySearchParamExtractor;
|
||||
@Autowired
|
||||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||
@Autowired
|
||||
private ModelConfig myModelConfig;
|
||||
@Autowired
|
||||
private FhirContext myContext;
|
||||
@Autowired
|
||||
private ISearchParamRegistry mySearchParamRegistry;
|
||||
@Autowired(required = false)
|
||||
private IResourceLinkResolver myResourceLinkResolver;
|
||||
|
||||
public void extractFromResource(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource) {
|
||||
/**
|
||||
* This method is responsible for scanning a resource for all of the search parameter instances. I.e. for all search parameters defined for
|
||||
* a given resource type, it extracts the associated indexes and populates {@literal theParams}.
|
||||
*/
|
||||
public void extractFromResource(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, Date theUpdateTime, boolean theFailOnInvalidReference) {
|
||||
IBaseResource resource = normalizeResource(theResource);
|
||||
|
||||
// All search parameter types except Reference
|
||||
extractSearchIndexParameters(theRequestDetails, theParams, resource, theEntity);
|
||||
|
||||
// Reference search parameters
|
||||
extractResourceLinks(theParams, theEntity, resource, theUpdateTime, theFailOnInvalidReference, theRequestDetails);
|
||||
|
||||
theParams.setUpdatedTime(theUpdateTime);
|
||||
}
|
||||
|
||||
private void extractSearchIndexParameters(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, IBaseResource theResource, ResourceTable theEntity) {
|
||||
|
||||
// Strings
|
||||
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamString> strings = extractSearchParamStrings(theResource);
|
||||
handleWarnings(theRequestDetails, myInterceptorBroadcaster, strings);
|
||||
theParams.myStringParams.addAll(strings);
|
||||
|
||||
// Numbers
|
||||
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamNumber> numbers = extractSearchParamNumber(theResource);
|
||||
handleWarnings(theRequestDetails, myInterceptorBroadcaster, numbers);
|
||||
theParams.myNumberParams.addAll(numbers);
|
||||
|
||||
// Quantities
|
||||
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamQuantity> quantities = extractSearchParamQuantity(theResource);
|
||||
handleWarnings(theRequestDetails, myInterceptorBroadcaster, quantities);
|
||||
theParams.myQuantityParams.addAll(quantities);
|
||||
|
||||
// Dates
|
||||
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamDate> dates = extractSearchParamDates(theResource);
|
||||
handleWarnings(theRequestDetails, myInterceptorBroadcaster, dates);
|
||||
theParams.myDateParams.addAll(dates);
|
||||
|
||||
// URIs
|
||||
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamUri> uris = extractSearchParamUri(theResource);
|
||||
handleWarnings(theRequestDetails, myInterceptorBroadcaster, uris);
|
||||
theParams.myUriParams.addAll(uris);
|
||||
|
||||
ourLog.trace("Storing date indexes: {}", theParams.myDateParams);
|
||||
|
||||
// Tokens (can result in both Token and String, as we index the display name for
|
||||
// the types: Coding, CodeableConcept)
|
||||
for (BaseResourceIndexedSearchParam next : extractSearchParamTokens(theResource)) {
|
||||
if (next instanceof ResourceIndexedSearchParamToken) {
|
||||
theParams.myTokenParams.add((ResourceIndexedSearchParamToken) next);
|
||||
|
@ -77,21 +122,182 @@ public class SearchParamExtractorService {
|
|||
}
|
||||
}
|
||||
|
||||
// Specials
|
||||
for (BaseResourceIndexedSearchParam next : extractSearchParamSpecial(theResource)) {
|
||||
if (next instanceof ResourceIndexedSearchParamCoords) {
|
||||
theParams.myCoordsParams.add((ResourceIndexedSearchParamCoords) next);
|
||||
}
|
||||
}
|
||||
|
||||
populateResourceTable(theParams.myStringParams, theEntity);
|
||||
// Do this after, because we add to strings during both string and token processing
|
||||
populateResourceTable(theParams.myNumberParams, theEntity);
|
||||
populateResourceTable(theParams.myQuantityParams, theEntity);
|
||||
populateResourceTable(theParams.myDateParams, theEntity);
|
||||
populateResourceTable(theParams.myUriParams, theEntity);
|
||||
populateResourceTable(theParams.myCoordsParams, theEntity);
|
||||
populateResourceTable(theParams.myTokenParams, theEntity);
|
||||
populateResourceTable(theParams.myStringParams, theEntity);
|
||||
populateResourceTable(theParams.myCoordsParams, theEntity);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a bit hacky, but if someone has manually populated a resource (ie. my working directly with the model
|
||||
* as opposed to by parsing a serialized instance) it's possible that they have put in contained resources
|
||||
* using {@link IBaseReference#setResource(IBaseResource)}, and those contained resources have not yet
|
||||
* ended up in the Resource.contained array, meaning that FHIRPath expressions won't be able to find them.
|
||||
*
|
||||
* As a result, we to a serialize-and-parse to normalize the object. This really only affects people who
|
||||
* are calling the JPA DAOs directly, but there are a few of those...
|
||||
*/
|
||||
private IBaseResource normalizeResource(IBaseResource theResource) {
|
||||
IParser parser = myContext.newJsonParser().setPrettyPrint(false);
|
||||
theResource = parser.parseResource(parser.encodeResourceToString(theResource));
|
||||
return theResource;
|
||||
}
|
||||
|
||||
private void extractResourceLinks(ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, Date theUpdateTime, boolean theFailOnInvalidReference, RequestDetails theRequest) {
|
||||
String resourceName = myContext.getResourceDefinition(theResource).getName();
|
||||
|
||||
ISearchParamExtractor.SearchParamSet<PathAndRef> refs = mySearchParamExtractor.extractResourceLinks(theResource);
|
||||
SearchParamExtractorService.handleWarnings(theRequest, myInterceptorBroadcaster, refs);
|
||||
|
||||
Map<String, IResourceLookup> resourceIdToResolvedTarget = new HashMap<>();
|
||||
for (PathAndRef nextPathAndRef : refs) {
|
||||
RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(resourceName, nextPathAndRef.getSearchParamName());
|
||||
extractResourceLinks(theParams, theEntity, theUpdateTime, searchParam, nextPathAndRef, theFailOnInvalidReference, theRequest, resourceIdToResolvedTarget);
|
||||
}
|
||||
|
||||
theEntity.setHasLinks(theParams.myLinks.size() > 0);
|
||||
}
|
||||
|
||||
private void extractResourceLinks(ResourceIndexedSearchParams theParams, ResourceTable theEntity, Date theUpdateTime, RuntimeSearchParam theRuntimeSearchParam, PathAndRef thePathAndRef, boolean theFailOnInvalidReference, RequestDetails theRequest, Map<String, IResourceLookup> theResourceIdToResolvedTarget) {
|
||||
IBaseReference nextReference = thePathAndRef.getRef();
|
||||
IIdType nextId = nextReference.getReferenceElement();
|
||||
String path = thePathAndRef.getPath();
|
||||
|
||||
/*
|
||||
* This can only really happen if the DAO is being called
|
||||
* programmatically with a Bundle (not through the FHIR REST API)
|
||||
* but Smile does this
|
||||
*/
|
||||
if (nextId.isEmpty() && nextReference.getResource() != null) {
|
||||
nextId = nextReference.getResource().getIdElement();
|
||||
}
|
||||
|
||||
theParams.myPopulatedResourceLinkParameters.add(thePathAndRef.getSearchParamName());
|
||||
|
||||
boolean canonical = thePathAndRef.isCanonical();
|
||||
if (LogicalReferenceHelper.isLogicalReference(myModelConfig, nextId) || canonical) {
|
||||
String value = nextId.getValue();
|
||||
ResourceLink resourceLink = ResourceLink.forLogicalReference(thePathAndRef.getPath(), theEntity, value, theUpdateTime);
|
||||
if (theParams.myLinks.add(resourceLink)) {
|
||||
ourLog.debug("Indexing remote resource reference URL: {}", nextId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String baseUrl = nextId.getBaseUrl();
|
||||
String typeString = nextId.getResourceType();
|
||||
if (isBlank(typeString)) {
|
||||
String msg = "Invalid resource reference found at path[" + path + "] - Does not contain resource type - " + nextId.getValue();
|
||||
if (theFailOnInvalidReference) {
|
||||
throw new InvalidRequestException(msg);
|
||||
} else {
|
||||
ourLog.debug(msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
RuntimeResourceDefinition resourceDefinition;
|
||||
try {
|
||||
resourceDefinition = myContext.getResourceDefinition(typeString);
|
||||
} catch (DataFormatException e) {
|
||||
String msg = "Invalid resource reference found at path[" + path + "] - Resource type is unknown or not supported on this server - " + nextId.getValue();
|
||||
if (theFailOnInvalidReference) {
|
||||
throw new InvalidRequestException(msg);
|
||||
} else {
|
||||
ourLog.debug(msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (theRuntimeSearchParam.hasTargets()) {
|
||||
if (!theRuntimeSearchParam.getTargets().contains(typeString)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (isNotBlank(baseUrl)) {
|
||||
if (!myModelConfig.getTreatBaseUrlsAsLocal().contains(baseUrl) && !myModelConfig.isAllowExternalReferences()) {
|
||||
String msg = myContext.getLocalizer().getMessage(BaseSearchParamExtractor.class, "externalReferenceNotAllowed", nextId.getValue());
|
||||
throw new InvalidRequestException(msg);
|
||||
} else {
|
||||
ResourceLink resourceLink = ResourceLink.forAbsoluteReference(thePathAndRef.getPath(), theEntity, nextId, theUpdateTime);
|
||||
if (theParams.myLinks.add(resourceLink)) {
|
||||
ourLog.debug("Indexing remote resource reference URL: {}", nextId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Class<? extends IBaseResource> type = resourceDefinition.getImplementingClass();
|
||||
String id = nextId.getIdPart();
|
||||
if (StringUtils.isBlank(id)) {
|
||||
String msg = "Invalid resource reference found at path[" + path + "] - Does not contain resource ID - " + nextId.getValue();
|
||||
if (theFailOnInvalidReference) {
|
||||
throw new InvalidRequestException(msg);
|
||||
} else {
|
||||
ourLog.debug(msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ResourceLink resourceLink;
|
||||
if (theFailOnInvalidReference) {
|
||||
|
||||
myResourceLinkResolver.validateTypeOrThrowException(type);
|
||||
resourceLink = resolveTargetAndCreateResourceLinkOrReturnNull(theEntity, theUpdateTime, theRuntimeSearchParam, path, thePathAndRef, nextId, typeString, type, nextReference, theRequest, theResourceIdToResolvedTarget);
|
||||
if (resourceLink == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
ResourceTable target;
|
||||
target = new ResourceTable();
|
||||
target.setResourceType(typeString);
|
||||
resourceLink = ResourceLink.forLocalReference(thePathAndRef.getPath(), theEntity, typeString, null, nextId.getIdPart(), theUpdateTime);
|
||||
|
||||
}
|
||||
|
||||
theParams.myLinks.add(resourceLink);
|
||||
}
|
||||
|
||||
private ResourceLink resolveTargetAndCreateResourceLinkOrReturnNull(ResourceTable theEntity, Date theUpdateTime, RuntimeSearchParam nextSpDef, String theNextPathsUnsplit, PathAndRef nextPathAndRef, IIdType theNextId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest, Map<String, IResourceLookup> theResourceIdToResolvedTarget) {
|
||||
/*
|
||||
* We keep a cache of resolved target resources. This is good since for some resource types, there
|
||||
* are multiple search parameters that map to the same element path within a resource (e.g.
|
||||
* Observation:patient and Observation.subject and we don't want to force a resolution of the
|
||||
* target any more times than we have to.
|
||||
*/
|
||||
|
||||
IResourceLookup targetResource = theResourceIdToResolvedTarget.get(theNextId.getValue());
|
||||
if (targetResource == null) {
|
||||
targetResource = myResourceLinkResolver.findTargetResource(nextSpDef, theNextPathsUnsplit, theNextId, theTypeString, theType, theReference, theRequest);
|
||||
}
|
||||
|
||||
if (targetResource == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
theResourceIdToResolvedTarget.put(theNextId.getValue(), targetResource);
|
||||
|
||||
String targetResourceType = targetResource.getResourceType();
|
||||
Long targetResourcePid = targetResource.getResourceId();
|
||||
String targetResourceIdPart = theNextId.getIdPart();
|
||||
return ResourceLink.forLocalReference(nextPathAndRef.getPath(), theEntity, targetResourceType, targetResourcePid, targetResourceIdPart, theUpdateTime);
|
||||
}
|
||||
|
||||
|
||||
static void handleWarnings(RequestDetails theRequestDetails, IInterceptorBroadcaster theInterceptorBroadcaster, ISearchParamExtractor.SearchParamSet<?> theSearchParamSet) {
|
||||
if (theSearchParamSet.getWarnings().isEmpty()) {
|
||||
return;
|
||||
|
|
|
@ -23,30 +23,23 @@ package ca.uhn.fhir.jpa.searchparam.matcher;
|
|||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceLinkExtractor;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
public class IndexedSearchParamExtractor {
|
||||
@Autowired
|
||||
private FhirContext myContext;
|
||||
@Autowired
|
||||
private SearchParamExtractorService mySearchParamExtractorService;
|
||||
@Autowired
|
||||
private ResourceLinkExtractor myResourceLinkExtractor;
|
||||
@Autowired
|
||||
private InlineResourceLinkResolver myInlineResourceLinkResolver;
|
||||
|
||||
public ResourceIndexedSearchParams extractIndexedSearchParams(IBaseResource theResource, RequestDetails theRequest) {
|
||||
ResourceTable entity = new ResourceTable();
|
||||
String resourceType = myContext.getResourceDefinition(theResource).getName();
|
||||
entity.setResourceType(resourceType);
|
||||
ResourceIndexedSearchParams resourceIndexedSearchParams = new ResourceIndexedSearchParams();
|
||||
mySearchParamExtractorService.extractFromResource(theRequest, resourceIndexedSearchParams, entity, theResource);
|
||||
myResourceLinkExtractor.extractResourceLinks(resourceIndexedSearchParams, entity, theResource, theResource.getMeta().getLastUpdated(), myInlineResourceLinkResolver, false, theRequest);
|
||||
mySearchParamExtractorService.extractFromResource(theRequest, resourceIndexedSearchParams, entity, theResource, theResource.getMeta().getLastUpdated(), false);
|
||||
return resourceIndexedSearchParams;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
package ca.uhn.fhir.jpa.searchparam.matcher;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Search Parameters
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2020 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
||||
import ca.uhn.fhir.jpa.model.cross.ResourceLookup;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
public class InlineResourceLinkResolver implements IResourceLinkResolver {
|
||||
|
||||
@Override
|
||||
public IResourceLookup findTargetResource(RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest) {
|
||||
|
||||
/*
|
||||
* TODO: JA - This gets used during runtime in-memory matching for subscription. It's not
|
||||
* really clear if it's useful or not.
|
||||
*/
|
||||
|
||||
ResourceTable target;
|
||||
target = new ResourceTable();
|
||||
target.setResourceType(theTypeString);
|
||||
return new ResourceLookup(theTypeString, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateTypeOrThrowException(Class<? extends IBaseResource> theType) {
|
||||
// When resolving reference in-memory for a single resource, there's nothing to validate
|
||||
}
|
||||
}
|
|
@ -33,7 +33,9 @@ public class SearchParamMatcher {
|
|||
private InMemoryResourceMatcher myInMemoryResourceMatcher;
|
||||
|
||||
public InMemoryMatchResult match(String theCriteria, IBaseResource theResource, RequestDetails theRequest) {
|
||||
|
||||
ResourceIndexedSearchParams resourceIndexedSearchParams = myIndexedSearchParamExtractor.extractIndexedSearchParams(theResource, theRequest);
|
||||
|
||||
return myInMemoryResourceMatcher.match(theCriteria, theResource, resourceIndexedSearchParams);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ public class IndexStressTest {
|
|||
|
||||
FhirContext ctx = FhirContext.forDstu3();
|
||||
IValidationSupport mockValidationSupport = mock(IValidationSupport.class);
|
||||
when(mockValidationSupport.getFhirContext()).thenReturn(ctx);
|
||||
IValidationSupport validationSupport = new CachingValidationSupport(new ValidationSupportChain(new DefaultProfileValidationSupport(ctx), mockValidationSupport));
|
||||
ISearchParamRegistry searchParamRegistry = mock(ISearchParamRegistry.class);
|
||||
SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ctx, validationSupport, searchParamRegistry);
|
||||
|
|
|
@ -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,39 @@
|
|||
package ca.uhn.fhir.rest.server.method;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Server Framework
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2020 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
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")
|
||||
|
|
|
@ -3,14 +3,16 @@ package ca.uhn.fhir.rest.server;
|
|||
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.model.api.annotation.ResourceDef;
|
||||
import java.util.List;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import org.hl7.fhir.instance.model.api.IBaseMetaType;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class CommonResourceSupertypeScannerTest {
|
||||
|
||||
private final CommonResourceSupertypeScanner scanner = new CommonResourceSupertypeScanner();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -612,8 +612,7 @@ public class XmlParserDstu2_1Test {
|
|||
String out = xmlParser.encodeResourceToString(patient);
|
||||
ourLog.info(out);
|
||||
|
||||
//@formatter:off
|
||||
assertThat(out, stringContainsInOrder("<identifier>",
|
||||
assertThat(out, stringContainsInOrder("<identifier>",
|
||||
"<type>",
|
||||
"<coding>",
|
||||
"<system value=\"http://hl7.org/fhir/v2/0203\"/>",
|
||||
|
@ -623,7 +622,6 @@ public class XmlParserDstu2_1Test {
|
|||
"<system value=\"SYS\"/>",
|
||||
"<value value=\"VAL\"/>",
|
||||
"</identifier>"));
|
||||
//@formatter:on
|
||||
|
||||
patient = ourCtx.newXmlParser().parseResource(Patient.class, out);
|
||||
assertEquals("http://hl7.org/fhir/v2/0203", patient.getIdentifier().get(0).getType().getCoding().get(0).getSystem());
|
||||
|
@ -645,120 +643,6 @@ public class XmlParserDstu2_1Test {
|
|||
assertEquals("2015-10-05", mo.getDateWrittenElement().getValueAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeAndParseMetaProfileAndTags() {
|
||||
Patient p = new Patient();
|
||||
p.addName().addFamily("FAMILY");
|
||||
|
||||
p.getMeta().addProfile("http://foo/Profile1");
|
||||
p.getMeta().addProfile("http://foo/Profile2");
|
||||
|
||||
p.getMeta().addTag().setSystem("scheme1").setCode("term1").setDisplay("label1");
|
||||
p.getMeta().addTag().setSystem("scheme2").setCode("term2").setDisplay("label2");
|
||||
|
||||
p.getMeta().addSecurity().setSystem("sec_scheme1").setCode("sec_term1").setDisplay("sec_label1");
|
||||
p.getMeta().addSecurity().setSystem("sec_scheme2").setCode("sec_term2").setDisplay("sec_label2");
|
||||
|
||||
String enc = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(p);
|
||||
ourLog.info(enc);
|
||||
|
||||
//@formatter:off
|
||||
assertThat(enc, stringContainsInOrder("<Patient xmlns=\"http://hl7.org/fhir\">",
|
||||
"<meta>",
|
||||
"<meta>",
|
||||
"<profile value=\"http://foo/Profile1\"/>",
|
||||
"<profile value=\"http://foo/Profile2\"/>",
|
||||
"<tag>",
|
||||
"<system value=\"scheme1\"/>",
|
||||
"<code value=\"term1\"/>",
|
||||
"<display value=\"label1\"/>",
|
||||
"</tag>",
|
||||
"<tag>",
|
||||
"<system value=\"scheme2\"/>",
|
||||
"<code value=\"term2\"/>",
|
||||
"<display value=\"label2\"/>",
|
||||
"</tag>",
|
||||
"</meta>",
|
||||
"</meta>",
|
||||
"<name>",
|
||||
"<family value=\"FAMILY\"/>",
|
||||
"</name>",
|
||||
"</Patient>"));
|
||||
//@formatter:on
|
||||
|
||||
Patient parsed = ourCtx.newXmlParser().parseResource(Patient.class, enc);
|
||||
List<UriType> gotLabels = parsed.getMeta().getProfile();
|
||||
assertEquals(2, gotLabels.size());
|
||||
UriType label = gotLabels.get(0);
|
||||
assertEquals("http://foo/Profile1", label.getValue());
|
||||
label = gotLabels.get(1);
|
||||
assertEquals("http://foo/Profile2", label.getValue());
|
||||
|
||||
List<Coding> tagList = parsed.getMeta().getTag();
|
||||
assertEquals(2, tagList.size());
|
||||
assertEquals("scheme1", tagList.get(0).getSystem());
|
||||
assertEquals("term1", tagList.get(0).getCode());
|
||||
assertEquals("label1", tagList.get(0).getDisplay());
|
||||
assertEquals("scheme2", tagList.get(1).getSystem());
|
||||
assertEquals("term2", tagList.get(1).getCode());
|
||||
assertEquals("label2", tagList.get(1).getDisplay());
|
||||
|
||||
tagList = parsed.getMeta().getSecurity();
|
||||
assertEquals(2, tagList.size());
|
||||
assertEquals("sec_scheme1", tagList.get(0).getSystem());
|
||||
assertEquals("sec_term1", tagList.get(0).getCode());
|
||||
assertEquals("sec_label1", tagList.get(0).getDisplay());
|
||||
assertEquals("sec_scheme2", tagList.get(1).getSystem());
|
||||
assertEquals("sec_term2", tagList.get(1).getCode());
|
||||
assertEquals("sec_label2", tagList.get(1).getDisplay());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeAndParseMetaProfiles() {
|
||||
Patient p = new Patient();
|
||||
p.addName().addFamily("FAMILY");
|
||||
|
||||
p.getMeta().addTag().setSystem("scheme1").setCode("term1").setDisplay("label1");
|
||||
p.getMeta().addTag().setSystem("scheme2").setCode("term2").setDisplay("label2");
|
||||
|
||||
String enc = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(p);
|
||||
ourLog.info(enc);
|
||||
|
||||
//@formatter:off
|
||||
assertThat(enc, stringContainsInOrder("<Patient xmlns=\"http://hl7.org/fhir\">",
|
||||
"<meta>",
|
||||
"<meta>",
|
||||
"<tag>",
|
||||
"<system value=\"scheme1\"/>",
|
||||
"<code value=\"term1\"/>",
|
||||
"<display value=\"label1\"/>",
|
||||
"</tag>",
|
||||
"<tag>",
|
||||
"<system value=\"scheme2\"/>",
|
||||
"<code value=\"term2\"/>",
|
||||
"<display value=\"label2\"/>",
|
||||
"</tag>",
|
||||
"</meta>",
|
||||
"</meta>",
|
||||
"<name>",
|
||||
"<family value=\"FAMILY\"/>",
|
||||
"</name>",
|
||||
"</Patient>"));
|
||||
//@formatter:on
|
||||
|
||||
Patient parsed = ourCtx.newXmlParser().parseResource(Patient.class, enc);
|
||||
assertThat(parsed.getMeta().getProfile(), empty());
|
||||
|
||||
List<Coding> tagList = parsed.getMeta().getTag();
|
||||
assertEquals(2, tagList.size());
|
||||
assertEquals("scheme1", tagList.get(0).getSystem());
|
||||
assertEquals("term1", tagList.get(0).getCode());
|
||||
assertEquals("label1", tagList.get(0).getDisplay());
|
||||
assertEquals("scheme2", tagList.get(1).getSystem());
|
||||
assertEquals("term2", tagList.get(1).getCode());
|
||||
assertEquals("label2", tagList.get(1).getDisplay());
|
||||
}
|
||||
|
||||
/**
|
||||
* See #336
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ public class XmlParserDstu3Test {
|
|||
}
|
||||
|
||||
/**
|
||||
* We specifically include extensions on CapabilityStatment even in
|
||||
* We specifically include extensions on CapabilityStatement even in
|
||||
* summary mode, since this is behaviour that people depend on
|
||||
*/
|
||||
@Test
|
||||
|
@ -839,7 +839,6 @@ public class XmlParserDstu3Test {
|
|||
ourLog.info(enc);
|
||||
|
||||
assertThat(enc, stringContainsInOrder("<Patient xmlns=\"http://hl7.org/fhir\">",
|
||||
"<meta>",
|
||||
"<meta>",
|
||||
"<profile value=\"http://foo/Profile1\"/>",
|
||||
"<profile value=\"http://foo/Profile2\"/>",
|
||||
|
@ -854,7 +853,6 @@ public class XmlParserDstu3Test {
|
|||
"<display value=\"label2\"/>",
|
||||
"</tag>",
|
||||
"</meta>",
|
||||
"</meta>",
|
||||
"<name>",
|
||||
"<family value=\"FAMILY\"/>",
|
||||
"</name>",
|
||||
|
@ -899,7 +897,6 @@ public class XmlParserDstu3Test {
|
|||
ourLog.info(enc);
|
||||
|
||||
assertThat(enc, stringContainsInOrder("<Patient xmlns=\"http://hl7.org/fhir\">",
|
||||
"<meta>",
|
||||
"<meta>",
|
||||
"<tag>",
|
||||
"<system value=\"scheme1\"/>",
|
||||
|
@ -912,7 +909,6 @@ public class XmlParserDstu3Test {
|
|||
"<display value=\"label2\"/>",
|
||||
"</tag>",
|
||||
"</meta>",
|
||||
"</meta>",
|
||||
"<name>",
|
||||
"<family value=\"FAMILY\"/>",
|
||||
"</name>",
|
||||
|
@ -1935,7 +1931,7 @@ public class XmlParserDstu3Test {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeUndeclaredBlock() throws Exception {
|
||||
public void testEncodeUndeclaredBlock() {
|
||||
FooMessageHeader.FooMessageSourceComponent source = new FooMessageHeader.FooMessageSourceComponent();
|
||||
source.getMessageHeaderApplicationId().setValue("APPID");
|
||||
source.setName("NAME");
|
||||
|
@ -1965,7 +1961,7 @@ public class XmlParserDstu3Test {
|
|||
Patient patient = new Patient();
|
||||
patient.addAddress().setUse(AddressUse.HOME);
|
||||
EnumFactory<AddressUse> fact = new AddressUseEnumFactory();
|
||||
PrimitiveType<AddressUse> enumeration = new Enumeration<AddressUse>(fact).setValue(AddressUse.HOME);
|
||||
PrimitiveType<AddressUse> enumeration = new Enumeration<>(fact).setValue(AddressUse.HOME);
|
||||
patient.addExtension().setUrl("urn:foo").setValue(enumeration);
|
||||
|
||||
String val = parser.encodeResourceToString(patient);
|
||||
|
@ -1981,7 +1977,7 @@ public class XmlParserDstu3Test {
|
|||
|
||||
@Test
|
||||
public void testEncodeWithContained() {
|
||||
List<Resource> contained = new ArrayList<Resource>();
|
||||
List<Resource> contained = new ArrayList<>();
|
||||
|
||||
// Will be added by reference
|
||||
Patient p = new Patient();
|
||||
|
@ -2037,7 +2033,7 @@ public class XmlParserDstu3Test {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeWithDontEncodeElements() throws Exception {
|
||||
public void testEncodeWithDontEncodeElements() {
|
||||
Patient patient = new Patient();
|
||||
patient.setId("123");
|
||||
patient.getMeta().addProfile("http://profile");
|
||||
|
@ -2092,7 +2088,7 @@ public class XmlParserDstu3Test {
|
|||
{
|
||||
IParser p = ourCtx.newXmlParser();
|
||||
p.setDontEncodeElements(Sets.newHashSet("Patient.meta"));
|
||||
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name")));
|
||||
p.setEncodeElements(new HashSet<>(Arrays.asList("Patient.name")));
|
||||
p.setPrettyPrint(true);
|
||||
String out = p.encodeResourceToString(patient);
|
||||
ourLog.info(out);
|
||||
|
@ -2106,7 +2102,7 @@ public class XmlParserDstu3Test {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeWithEncodeElements() throws Exception {
|
||||
public void testEncodeWithEncodeElements() {
|
||||
Patient patient = new Patient();
|
||||
patient.getMeta().addProfile("http://profile");
|
||||
patient.addName().setFamily("FAMILY");
|
||||
|
@ -2129,7 +2125,7 @@ public class XmlParserDstu3Test {
|
|||
}
|
||||
{
|
||||
IParser p = ourCtx.newXmlParser();
|
||||
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name")));
|
||||
p.setEncodeElements(new HashSet<>(Arrays.asList("Patient.name")));
|
||||
p.setPrettyPrint(true);
|
||||
String out = p.encodeResourceToString(bundle);
|
||||
ourLog.info(out);
|
||||
|
@ -2140,7 +2136,7 @@ public class XmlParserDstu3Test {
|
|||
}
|
||||
{
|
||||
IParser p = ourCtx.newXmlParser();
|
||||
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient")));
|
||||
p.setEncodeElements(new HashSet<>(Arrays.asList("Patient")));
|
||||
p.setPrettyPrint(true);
|
||||
String out = p.encodeResourceToString(bundle);
|
||||
ourLog.info(out);
|
||||
|
@ -2153,7 +2149,7 @@ public class XmlParserDstu3Test {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeWithEncodeElementsAppliesToChildResourcesOnly() throws Exception {
|
||||
public void testEncodeWithEncodeElementsAppliesToChildResourcesOnly() {
|
||||
Patient patient = new Patient();
|
||||
patient.getMeta().addProfile("http://profile");
|
||||
patient.addName().setFamily("FAMILY");
|
||||
|
@ -2208,7 +2204,7 @@ public class XmlParserDstu3Test {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testMoreExtensions() throws Exception {
|
||||
public void testMoreExtensions() {
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.addIdentifier().setUse(IdentifierUse.OFFICIAL).setSystem("urn:example").setValue("7000135");
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -5,10 +5,7 @@ import ca.uhn.fhir.rest.api.Constants;
|
|||
import ca.uhn.fhir.test.BaseTest;
|
||||
import ca.uhn.fhir.util.StopWatch;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.io.Resources;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.io.output.NullWriter;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
@ -21,7 +18,6 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
@ -30,8 +26,10 @@ import java.util.concurrent.TimeUnit;
|
|||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.stringContainsInOrder;
|
||||
import static org.hamcrest.core.IsNot.not;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class JsonParserR4Test extends BaseTest {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(JsonParserR4Test.class);
|
||||
|
@ -156,10 +154,12 @@ public class JsonParserR4Test extends BaseTest {
|
|||
|
||||
@Test
|
||||
public void testParseBundleWithMultipleNestedContainedResources() throws Exception {
|
||||
URL url = Resources.getResource("bundle-with-two-patient-resources.json");
|
||||
String text = Resources.toString(url, Charsets.UTF_8);
|
||||
String text = loadResource("/bundle-with-two-patient-resources.json");
|
||||
|
||||
Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, text);
|
||||
assertEquals(Boolean.TRUE, bundle.getUserData(BaseParser.RESOURCE_CREATED_BY_PARSER));
|
||||
assertEquals(Boolean.TRUE, bundle.getEntry().get(0).getResource().getUserData(BaseParser.RESOURCE_CREATED_BY_PARSER));
|
||||
assertEquals(Boolean.TRUE, bundle.getEntry().get(1).getResource().getUserData(BaseParser.RESOURCE_CREATED_BY_PARSER));
|
||||
|
||||
assertEquals("12346", getPatientIdValue(bundle, 0));
|
||||
assertEquals("12345", getPatientIdValue(bundle, 1));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -7,6 +7,7 @@ import ca.uhn.fhir.rest.annotation.Search;
|
|||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.api.SummaryEnum;
|
||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import com.google.common.base.Charsets;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
@ -18,9 +19,12 @@ 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.hamcrest.CoreMatchers;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.MedicationRequest;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.Reference;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
|
@ -32,14 +36,13 @@ import java.util.List;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsStringIgnoringCase;
|
||||
import static org.hamcrest.CoreMatchers.containsStringIgnoringCase;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.containsStringIgnoringCase;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
||||
|
||||
public class SummaryParamR4Test {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SummaryParamR4Test.class);
|
||||
|
@ -223,7 +226,7 @@ public class SummaryParamR4Test {
|
|||
assertThat(responseContent, (containsString("entry")));
|
||||
assertThat(responseContent, (containsString(">TEXT<")));
|
||||
assertThat(responseContent, (containsString("Medication/123")));
|
||||
assertThat(responseContent, not(CoreMatchers.containsStringIgnoringCase("note")));
|
||||
assertThat(responseContent, not(containsStringIgnoringCase("note")));
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -1,5 +1,25 @@
|
|||
package ca.uhn.fhir.test.utilities;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Test Utilities
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2020 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.springframework.aop.framework.AopProxyUtils;
|
||||
|
||||
|
|
|
@ -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, "/*");
|
||||
|
||||
|
|
|
@ -644,7 +644,6 @@ public class XmlParserDstu2_1Test {
|
|||
|
||||
//@formatter:off
|
||||
assertThat(enc, stringContainsInOrder("<Patient xmlns=\"http://hl7.org/fhir\">",
|
||||
"<meta>",
|
||||
"<meta>",
|
||||
"<profile value=\"http://foo/Profile1\"/>",
|
||||
"<profile value=\"http://foo/Profile2\"/>",
|
||||
|
@ -659,7 +658,6 @@ public class XmlParserDstu2_1Test {
|
|||
"<display value=\"label2\"/>",
|
||||
"</tag>",
|
||||
"</meta>",
|
||||
"</meta>",
|
||||
"<name>",
|
||||
"<family value=\"FAMILY\"/>",
|
||||
"</name>",
|
||||
|
@ -706,7 +704,6 @@ public class XmlParserDstu2_1Test {
|
|||
|
||||
//@formatter:off
|
||||
assertThat(enc, stringContainsInOrder("<Patient xmlns=\"http://hl7.org/fhir\">",
|
||||
"<meta>",
|
||||
"<meta>",
|
||||
"<tag>",
|
||||
"<system value=\"scheme1\"/>",
|
||||
|
@ -719,7 +716,6 @@ public class XmlParserDstu2_1Test {
|
|||
"<display value=\"label2\"/>",
|
||||
"</tag>",
|
||||
"</meta>",
|
||||
"</meta>",
|
||||
"<name>",
|
||||
"<family value=\"FAMILY\"/>",
|
||||
"</name>",
|
||||
|
|
|
@ -755,7 +755,6 @@ public class Dstu3XmlParserTest {
|
|||
ourLog.info(enc);
|
||||
|
||||
assertThat(enc, stringContainsInOrder("<Patient xmlns=\"http://hl7.org/fhir\">",
|
||||
"<meta>",
|
||||
"<meta>",
|
||||
"<profile value=\"http://foo/Profile1\"/>",
|
||||
"<profile value=\"http://foo/Profile2\"/>",
|
||||
|
@ -770,7 +769,6 @@ public class Dstu3XmlParserTest {
|
|||
"<display value=\"label2\"/>",
|
||||
"</tag>",
|
||||
"</meta>",
|
||||
"</meta>",
|
||||
"<name>",
|
||||
"<family value=\"FAMILY\"/>",
|
||||
"</name>",
|
||||
|
@ -815,7 +813,6 @@ public class Dstu3XmlParserTest {
|
|||
ourLog.info(enc);
|
||||
|
||||
assertThat(enc, stringContainsInOrder("<Patient xmlns=\"http://hl7.org/fhir\">",
|
||||
"<meta>",
|
||||
"<meta>",
|
||||
"<tag>",
|
||||
"<system value=\"scheme1\"/>",
|
||||
|
@ -828,7 +825,6 @@ public class Dstu3XmlParserTest {
|
|||
"<display value=\"label2\"/>",
|
||||
"</tag>",
|
||||
"</meta>",
|
||||
"</meta>",
|
||||
"<name>",
|
||||
"<family value=\"FAMILY\"/>",
|
||||
"</name>",
|
||||
|
|
10
pom.xml
10
pom.xml
|
@ -76,14 +76,14 @@
|
|||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>java-hamcrest</artifactId>
|
||||
<artifactId>hamcrest</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -1278,11 +1278,17 @@
|
|||
<artifactId>jscience</artifactId>
|
||||
<version>4.3.1</version>
|
||||
</dependency>
|
||||
<!-- TODO: remove this once we're fully switched over to hamcrest 2.2 -->
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>java-hamcrest</artifactId>
|
||||
<version>2.0.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest</artifactId>
|
||||
<version>2.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-core</artifactId>
|
||||
|
|
Loading…
Reference in New Issue