Merge remote-tracking branch 'remotes/origin/master' into im_20200316_lastn_operation_elasticsearch

This commit is contained in:
ianmarshall 2020-04-17 17:39:27 -04:00
commit 540375a7eb
65 changed files with 1286 additions and 792 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 \".\""));
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -106,6 +106,11 @@ class IncludeParameter extends BaseQueryParameter {
return null;
}
@Override
protected boolean supportsRepetition() {
return myInstantiableCollectionType != null;
}
@Override
public boolean handlesMissing() {
return true;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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=", "&amp;_getpagesoffset=2&amp;_count=2&amp;_bundletype=searchset\"/>",
"</link>"));
//@formatter:on
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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