Validate include statements for JPA (#3331)
* Validate include statements for JPA * Add changelog * Optmize test fix
This commit is contained in:
parent
cd29d369b6
commit
3f42414287
|
@ -1,5 +1,6 @@
|
||||||
package ca.uhn.fhir.model.api;
|
package ca.uhn.fhir.model.api;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
|
@ -34,6 +35,9 @@ import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
* equality. Prior to HAPI 1.2 (and FHIR DSTU2) the recurse property did not exist, so this may merit consideration when
|
* equality. Prior to HAPI 1.2 (and FHIR DSTU2) the recurse property did not exist, so this may merit consideration when
|
||||||
* upgrading servers.
|
* upgrading servers.
|
||||||
* </p>
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Note on thrwead safety: This class is not thread safe.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
public class Include implements Serializable {
|
public class Include implements Serializable {
|
||||||
|
|
||||||
|
@ -42,6 +46,9 @@ public class Include implements Serializable {
|
||||||
private final boolean myImmutable;
|
private final boolean myImmutable;
|
||||||
private boolean myIterate;
|
private boolean myIterate;
|
||||||
private String myValue;
|
private String myValue;
|
||||||
|
private String myParamType;
|
||||||
|
private String myParamName;
|
||||||
|
private String myParamTargetType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for <b>non-recursive</b> include
|
* Constructor for <b>non-recursive</b> include
|
||||||
|
@ -50,8 +57,7 @@ public class Include implements Serializable {
|
||||||
* The <code>_include</code> value, e.g. "Patient:name"
|
* The <code>_include</code> value, e.g. "Patient:name"
|
||||||
*/
|
*/
|
||||||
public Include(String theValue) {
|
public Include(String theValue) {
|
||||||
myValue = theValue;
|
this(theValue, false);
|
||||||
myImmutable = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -63,9 +69,7 @@ public class Include implements Serializable {
|
||||||
* Should the include recurse
|
* Should the include recurse
|
||||||
*/
|
*/
|
||||||
public Include(String theValue, boolean theIterate) {
|
public Include(String theValue, boolean theIterate) {
|
||||||
myValue = theValue;
|
this(theValue, theIterate, false);
|
||||||
myIterate = theIterate;
|
|
||||||
myImmutable = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -77,7 +81,7 @@ public class Include implements Serializable {
|
||||||
* Should the include recurse
|
* Should the include recurse
|
||||||
*/
|
*/
|
||||||
public Include(String theValue, boolean theIterate, boolean theImmutable) {
|
public Include(String theValue, boolean theIterate, boolean theImmutable) {
|
||||||
myValue = theValue;
|
setValue(theValue);
|
||||||
myIterate = theIterate;
|
myIterate = theIterate;
|
||||||
myImmutable = theImmutable;
|
myImmutable = theImmutable;
|
||||||
}
|
}
|
||||||
|
@ -128,41 +132,21 @@ public class Include implements Serializable {
|
||||||
* Returns the portion of the value before the first colon
|
* Returns the portion of the value before the first colon
|
||||||
*/
|
*/
|
||||||
public String getParamType() {
|
public String getParamType() {
|
||||||
int firstColon = myValue.indexOf(':');
|
return myParamType;
|
||||||
if (firstColon == -1 || firstColon == myValue.length() - 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return myValue.substring(0, firstColon);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the portion of the value after the first colon but before the second colon
|
* Returns the portion of the value after the first colon but before the second colon
|
||||||
*/
|
*/
|
||||||
public String getParamName() {
|
public String getParamName() {
|
||||||
int firstColon = myValue.indexOf(':');
|
return myParamName;
|
||||||
if (firstColon == -1 || firstColon == myValue.length() - 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int secondColon = myValue.indexOf(':', firstColon + 1);
|
|
||||||
if (secondColon != -1) {
|
|
||||||
return myValue.substring(firstColon + 1, secondColon);
|
|
||||||
}
|
|
||||||
return myValue.substring(firstColon + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the portion of the string after the second colon, or null if there are not two colons in the value.
|
* Returns the portion of the string after the second colon, or null if there are not two colons in the value.
|
||||||
*/
|
*/
|
||||||
public String getParamTargetType() {
|
public String getParamTargetType() {
|
||||||
int firstColon = myValue.indexOf(':');
|
return myParamTargetType;
|
||||||
if (firstColon == -1 || firstColon == myValue.length() - 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int secondColon = myValue.indexOf(':', firstColon + 1);
|
|
||||||
if (secondColon != -1) {
|
|
||||||
return myValue.substring(secondColon + 1);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,7 +191,34 @@ public class Include implements Serializable {
|
||||||
if (myImmutable) {
|
if (myImmutable) {
|
||||||
throw new IllegalStateException("Can not change the value of this include");
|
throw new IllegalStateException("Can not change the value of this include");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String value = defaultString(theValue);
|
||||||
|
|
||||||
|
int firstColon = value.indexOf(':');
|
||||||
|
String paramType;
|
||||||
|
String paramName;
|
||||||
|
String paramTargetType;
|
||||||
|
if (firstColon == -1 || firstColon == value.length() - 1) {
|
||||||
|
paramType = null;
|
||||||
|
paramName = null;
|
||||||
|
paramTargetType = null;
|
||||||
|
} else {
|
||||||
|
paramType = value.substring(0, firstColon);
|
||||||
|
int secondColon = value.indexOf(':', firstColon + 1);
|
||||||
|
if (secondColon == -1) {
|
||||||
|
paramName = value.substring(firstColon + 1);
|
||||||
|
paramTargetType = null;
|
||||||
|
} else {
|
||||||
|
paramName = value.substring(firstColon + 1, secondColon);
|
||||||
|
paramTargetType = value.substring(secondColon + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
myParamType = paramType;
|
||||||
|
myParamName = paramName;
|
||||||
|
myParamTargetType = paramTargetType;
|
||||||
myValue = theValue;
|
myValue = theValue;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -290,6 +290,7 @@ public class Constants {
|
||||||
public static final String SUBSCRIPTION_MULTITYPE_SUFFIX = "]";
|
public static final String SUBSCRIPTION_MULTITYPE_SUFFIX = "]";
|
||||||
public static final String SUBSCRIPTION_MULTITYPE_STAR = "*";
|
public static final String SUBSCRIPTION_MULTITYPE_STAR = "*";
|
||||||
public static final String SUBSCRIPTION_STAR_CRITERIA = SUBSCRIPTION_MULTITYPE_PREFIX + SUBSCRIPTION_MULTITYPE_STAR + SUBSCRIPTION_MULTITYPE_SUFFIX;
|
public static final String SUBSCRIPTION_STAR_CRITERIA = SUBSCRIPTION_MULTITYPE_PREFIX + SUBSCRIPTION_MULTITYPE_STAR + SUBSCRIPTION_MULTITYPE_SUFFIX;
|
||||||
|
public static final String INCLUDE_STAR = "*";
|
||||||
|
|
||||||
static {
|
static {
|
||||||
CHARSET_UTF8 = StandardCharsets.UTF_8;
|
CHARSET_UTF8 = StandardCharsets.UTF_8;
|
||||||
|
|
|
@ -121,6 +121,7 @@ ca.uhn.fhir.jpa.patch.FhirPatch.invalidMoveSourceIndex=Invalid move source index
|
||||||
ca.uhn.fhir.jpa.patch.FhirPatch.invalidMoveDestinationIndex=Invalid move destination index {0} for path {1} - Only have {2} existing entries
|
ca.uhn.fhir.jpa.patch.FhirPatch.invalidMoveDestinationIndex=Invalid move destination index {0} for path {1} - Only have {2} existing entries
|
||||||
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.externalReferenceNotAllowed=Resource contains external reference to URL "{0}" but this server is not configured to allow external references
|
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.externalReferenceNotAllowed=Resource contains external reference to URL "{0}" but this server is not configured to allow external references
|
||||||
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.failedToExtractPaths=Failed to extract values from resource using FHIRPath "{0}": {1}
|
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.failedToExtractPaths=Failed to extract values from resource using FHIRPath "{0}": {1}
|
||||||
|
ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl.invalidInclude=Invalid {0} parameter value: "{1}". {2}
|
||||||
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.invalidQuantityPrefix=Unable to handle quantity prefix "{0}" for value: {1}
|
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.invalidQuantityPrefix=Unable to handle quantity prefix "{0}" for value: {1}
|
||||||
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.invalidNumberPrefix=Unable to handle number prefix "{0}" for value: {1}
|
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.invalidNumberPrefix=Unable to handle number prefix "{0}" for value: {1}
|
||||||
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.sourceParamDisabled=The _source parameter is disabled on this server
|
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.sourceParamDisabled=The _source parameter is disabled on this server
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
type: fix
|
||||||
|
issue: 3331
|
||||||
|
title: "The JPA server will now validate `_include` and `_revinclude` statements before beginning
|
||||||
|
search processing. This prevents an ugly JPA error when some invalid params are used."
|
|
@ -30,9 +30,11 @@ import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IDao;
|
import ca.uhn.fhir.jpa.api.dao.IDao;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
|
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
|
||||||
|
import ca.uhn.fhir.jpa.dao.BaseStorageDao;
|
||||||
import ca.uhn.fhir.jpa.dao.IResultIterator;
|
import ca.uhn.fhir.jpa.dao.IResultIterator;
|
||||||
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
||||||
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
|
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
|
||||||
|
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference;
|
||||||
import ca.uhn.fhir.jpa.entity.Search;
|
import ca.uhn.fhir.jpa.entity.Search;
|
||||||
import ca.uhn.fhir.jpa.entity.SearchInclude;
|
import ca.uhn.fhir.jpa.entity.SearchInclude;
|
||||||
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
||||||
|
@ -49,6 +51,7 @@ import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
import ca.uhn.fhir.model.api.Include;
|
import ca.uhn.fhir.model.api.Include;
|
||||||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.SearchTotalModeEnum;
|
import ca.uhn.fhir.rest.api.SearchTotalModeEnum;
|
||||||
import ca.uhn.fhir.rest.api.SummaryEnum;
|
import ca.uhn.fhir.rest.api.SummaryEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
|
@ -65,8 +68,10 @@ import ca.uhn.fhir.rest.server.method.PageMethodBinding;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.rest.server.util.ICachedSearchDetails;
|
import ca.uhn.fhir.rest.server.util.ICachedSearchDetails;
|
||||||
|
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
||||||
import ca.uhn.fhir.util.AsyncUtil;
|
import ca.uhn.fhir.util.AsyncUtil;
|
||||||
import ca.uhn.fhir.util.StopWatch;
|
import ca.uhn.fhir.util.StopWatch;
|
||||||
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
import co.elastic.apm.api.ElasticApm;
|
import co.elastic.apm.api.ElasticApm;
|
||||||
import co.elastic.apm.api.Span;
|
import co.elastic.apm.api.Span;
|
||||||
import co.elastic.apm.api.Transaction;
|
import co.elastic.apm.api.Transaction;
|
||||||
|
@ -109,8 +114,11 @@ import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
@Component("mySearchCoordinatorSvc")
|
@Component("mySearchCoordinatorSvc")
|
||||||
public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
|
@ -152,6 +160,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory;
|
private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory;
|
||||||
@Autowired
|
@Autowired
|
||||||
private IRequestPartitionHelperSvc myRequestPartitionHelperService;
|
private IRequestPartitionHelperSvc myRequestPartitionHelperService;
|
||||||
|
@Autowired
|
||||||
|
private ISearchParamRegistry mySearchParamRegistry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
@ -318,6 +328,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
.add(SearchParameterMap.class, theParams);
|
.add(SearchParameterMap.class, theParams);
|
||||||
CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.STORAGE_PRESEARCH_REGISTERED, params);
|
CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.STORAGE_PRESEARCH_REGISTERED, params);
|
||||||
|
|
||||||
|
validateSearch(theParams);
|
||||||
|
|
||||||
Class<? extends IBaseResource> resourceTypeClass = myContext.getResourceDefinition(theResourceType).getImplementingClass();
|
Class<? extends IBaseResource> resourceTypeClass = myContext.getResourceDefinition(theResourceType).getImplementingClass();
|
||||||
final ISearchBuilder sb = mySearchBuilderFactory.newSearchBuilder(theCallingDao, theResourceType, resourceTypeClass);
|
final ISearchBuilder sb = mySearchBuilderFactory.newSearchBuilder(theCallingDao, theResourceType, resourceTypeClass);
|
||||||
sb.setFetchSize(mySyncSize);
|
sb.setFetchSize(mySyncSize);
|
||||||
|
@ -356,6 +368,56 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateSearch(SearchParameterMap theParams) {
|
||||||
|
validateIncludes(theParams.getIncludes(), Constants.PARAM_INCLUDE);
|
||||||
|
validateIncludes(theParams.getRevIncludes(), Constants.PARAM_REVINCLUDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateIncludes(Set<Include> includes, String name) {
|
||||||
|
for (Include next : includes) {
|
||||||
|
String value = next.getValue();
|
||||||
|
if (value.equals(Constants.INCLUDE_STAR) || isBlank(value)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String paramType = next.getParamType();
|
||||||
|
String paramName = next.getParamName();
|
||||||
|
String paramTargetType = next.getParamTargetType();
|
||||||
|
|
||||||
|
if (isBlank(paramType) || isBlank(paramName)) {
|
||||||
|
String msg = myContext.getLocalizer().getMessageSanitized(SearchCoordinatorSvcImpl.class, "invalidInclude", name, value, "");
|
||||||
|
throw new InvalidRequestException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!myDaoRegistry.isResourceTypeSupported(paramType)) {
|
||||||
|
String resourceTypeMsg = myContext.getLocalizer().getMessageSanitized(PredicateBuilderReference.class, "invalidResourceType", paramType);
|
||||||
|
String msg = myContext.getLocalizer().getMessage(SearchCoordinatorSvcImpl.class, "invalidInclude", UrlUtil.sanitizeUrlPart(name), UrlUtil.sanitizeUrlPart(value), resourceTypeMsg); // last param is pre-sanitized
|
||||||
|
throw new InvalidRequestException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNotBlank(paramTargetType) && !myDaoRegistry.isResourceTypeSupported(paramTargetType)) {
|
||||||
|
String resourceTypeMsg = myContext.getLocalizer().getMessageSanitized(PredicateBuilderReference.class, "invalidResourceType", paramTargetType);
|
||||||
|
String msg = myContext.getLocalizer().getMessage(SearchCoordinatorSvcImpl.class, "invalidInclude", UrlUtil.sanitizeUrlPart(name), UrlUtil.sanitizeUrlPart(value), resourceTypeMsg); // last param is pre-sanitized
|
||||||
|
throw new InvalidRequestException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Constants.INCLUDE_STAR.equals(paramName) && mySearchParamRegistry.getActiveSearchParam(paramType, paramName) == null) {
|
||||||
|
List<String> validNames = mySearchParamRegistry
|
||||||
|
.getActiveSearchParams(paramType)
|
||||||
|
.values()
|
||||||
|
.stream()
|
||||||
|
.filter(t -> t.getParamType() == RestSearchParameterTypeEnum.REFERENCE)
|
||||||
|
.map(t -> UrlUtil.sanitizeUrlPart(t.getName()))
|
||||||
|
.sorted()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
String searchParamMessage = myContext.getLocalizer().getMessage(BaseStorageDao.class, "invalidSearchParameter", UrlUtil.sanitizeUrlPart(paramName), UrlUtil.sanitizeUrlPart(paramType), validNames);
|
||||||
|
String msg = myContext.getLocalizer().getMessage(SearchCoordinatorSvcImpl.class, "invalidInclude", UrlUtil.sanitizeUrlPart(name), UrlUtil.sanitizeUrlPart(value), searchParamMessage); // last param is pre-sanitized
|
||||||
|
throw new InvalidRequestException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Integer> getSearchTotal(String theUuid) {
|
public Optional<Integer> getSearchTotal(String theUuid) {
|
||||||
SearchTask task = myIdToSearchTask.get(theUuid);
|
SearchTask task = myIdToSearchTask.get(theUuid);
|
||||||
|
@ -674,6 +736,95 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
(myParams.getSearchTotalMode() == null && SearchTotalModeEnum.ACCURATE.equals(myDaoConfig.getDefaultTotalMode()));
|
(myParams.getSearchTotalMode() == null && SearchTotalModeEnum.ACCURATE.equals(myDaoConfig.getDefaultTotalMode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isWantOnlyCount(SearchParameterMap myParams) {
|
||||||
|
return SummaryEnum.COUNT.equals(myParams.getSummaryMode())
|
||||||
|
| INTEGER_0.equals(myParams.getCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void populateSearchEntity(SearchParameterMap theParams, String theResourceType, String theSearchUuid, String theQueryString, Search theSearch, RequestPartitionId theRequestPartitionId) {
|
||||||
|
theSearch.setDeleted(false);
|
||||||
|
theSearch.setUuid(theSearchUuid);
|
||||||
|
theSearch.setCreated(new Date());
|
||||||
|
theSearch.setTotalCount(null);
|
||||||
|
theSearch.setNumFound(0);
|
||||||
|
theSearch.setPreferredPageSize(theParams.getCount());
|
||||||
|
theSearch.setSearchType(theParams.getEverythingMode() != null ? SearchTypeEnum.EVERYTHING : SearchTypeEnum.SEARCH);
|
||||||
|
theSearch.setLastUpdated(theParams.getLastUpdated());
|
||||||
|
theSearch.setResourceType(theResourceType);
|
||||||
|
theSearch.setStatus(SearchStatusEnum.LOADING);
|
||||||
|
theSearch.setSearchQueryString(theQueryString, theRequestPartitionId);
|
||||||
|
|
||||||
|
if (theParams.hasIncludes()) {
|
||||||
|
for (Include next : theParams.getIncludes()) {
|
||||||
|
theSearch.addInclude(new SearchInclude(theSearch, next.getValue(), false, next.isRecurse()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Include next : theParams.getRevIncludes()) {
|
||||||
|
theSearch.addInclude(new SearchInclude(theSearch, next.getValue(), true, next.isRecurse()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link Pageable} using a start and end index
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
@Nullable
|
||||||
|
public static Pageable toPage(final int theFromIndex, int theToIndex) {
|
||||||
|
int pageSize = theToIndex - theFromIndex;
|
||||||
|
if (pageSize < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pageIndex = theFromIndex / pageSize;
|
||||||
|
|
||||||
|
Pageable page = new AbstractPageRequest(pageIndex, pageSize) {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getOffset() {
|
||||||
|
return theFromIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Sort getSort() {
|
||||||
|
return Sort.unsorted();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pageable next() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pageable previous() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pageable first() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pageable withPage(int theI) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void verifySearchHasntFailedOrThrowInternalErrorException(Search theSearch) {
|
||||||
|
if (theSearch.getStatus() == SearchStatusEnum.FAILED) {
|
||||||
|
Integer status = theSearch.getFailureCode();
|
||||||
|
status = defaultIfNull(status, 500);
|
||||||
|
|
||||||
|
String message = theSearch.getFailureMessage();
|
||||||
|
throw BaseServerResponseException.newInstance(status, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A search task is a Callable task that runs in
|
* A search task is a Callable task that runs in
|
||||||
* a thread pool to handle an individual search. One instance
|
* a thread pool to handle an individual search. One instance
|
||||||
|
@ -1253,93 +1404,4 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isWantOnlyCount(SearchParameterMap myParams) {
|
|
||||||
return SummaryEnum.COUNT.equals(myParams.getSummaryMode())
|
|
||||||
| INTEGER_0.equals(myParams.getCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void populateSearchEntity(SearchParameterMap theParams, String theResourceType, String theSearchUuid, String theQueryString, Search theSearch, RequestPartitionId theRequestPartitionId) {
|
|
||||||
theSearch.setDeleted(false);
|
|
||||||
theSearch.setUuid(theSearchUuid);
|
|
||||||
theSearch.setCreated(new Date());
|
|
||||||
theSearch.setTotalCount(null);
|
|
||||||
theSearch.setNumFound(0);
|
|
||||||
theSearch.setPreferredPageSize(theParams.getCount());
|
|
||||||
theSearch.setSearchType(theParams.getEverythingMode() != null ? SearchTypeEnum.EVERYTHING : SearchTypeEnum.SEARCH);
|
|
||||||
theSearch.setLastUpdated(theParams.getLastUpdated());
|
|
||||||
theSearch.setResourceType(theResourceType);
|
|
||||||
theSearch.setStatus(SearchStatusEnum.LOADING);
|
|
||||||
theSearch.setSearchQueryString(theQueryString, theRequestPartitionId);
|
|
||||||
|
|
||||||
if (theParams.hasIncludes()) {
|
|
||||||
for (Include next : theParams.getIncludes()) {
|
|
||||||
theSearch.addInclude(new SearchInclude(theSearch, next.getValue(), false, next.isRecurse()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Include next : theParams.getRevIncludes()) {
|
|
||||||
theSearch.addInclude(new SearchInclude(theSearch, next.getValue(), true, next.isRecurse()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a {@link Pageable} using a start and end index
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
|
||||||
@Nullable
|
|
||||||
public static Pageable toPage(final int theFromIndex, int theToIndex) {
|
|
||||||
int pageSize = theToIndex - theFromIndex;
|
|
||||||
if (pageSize < 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
int pageIndex = theFromIndex / pageSize;
|
|
||||||
|
|
||||||
Pageable page = new AbstractPageRequest(pageIndex, pageSize) {
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getOffset() {
|
|
||||||
return theFromIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Sort getSort() {
|
|
||||||
return Sort.unsorted();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pageable next() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pageable previous() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pageable first() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pageable withPage(int theI) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return page;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void verifySearchHasntFailedOrThrowInternalErrorException(Search theSearch) {
|
|
||||||
if (theSearch.getStatus() == SearchStatusEnum.FAILED) {
|
|
||||||
Integer status = theSearch.getFailureCode();
|
|
||||||
status = defaultIfNull(status, 500);
|
|
||||||
|
|
||||||
String message = theSearch.getFailureMessage();
|
|
||||||
throw BaseServerResponseException.newInstance(status, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,11 +106,10 @@ public class FhirResourceDaoR4SearchIncludeTest extends BaseJpaR4Test {
|
||||||
SearchParameterMap map = SearchParameterMap.newSynchronous()
|
SearchParameterMap map = SearchParameterMap.newSynchronous()
|
||||||
.addInclude(new Include("CarePlan.patient"));
|
.addInclude(new Include("CarePlan.patient"));
|
||||||
try {
|
try {
|
||||||
IBundleProvider results = myCarePlanDao.search(map);
|
myCarePlanDao.search(map);
|
||||||
List<String> ids = toUnqualifiedVersionlessIdValues(results);
|
|
||||||
assertThat(ids.toString(), ids, containsInAnyOrder("CarePlan/CP-1"));
|
|
||||||
} catch (Exception e) {
|
|
||||||
fail();
|
fail();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// good
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next verify it with the ":" syntax
|
// Next verify it with the ":" syntax
|
||||||
|
|
|
@ -155,6 +155,7 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static ca.uhn.fhir.rest.api.Constants.PARAM_TYPE;
|
import static ca.uhn.fhir.rest.api.Constants.PARAM_TYPE;
|
||||||
import static org.apache.commons.lang3.StringUtils.countMatches;
|
import static org.apache.commons.lang3.StringUtils.countMatches;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.leftPad;
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.contains;
|
import static org.hamcrest.Matchers.contains;
|
||||||
|
@ -5118,7 +5119,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
|
||||||
StringOrListParam or = new StringOrListParam();
|
StringOrListParam or = new StringOrListParam();
|
||||||
or.addOr(new StringParam("A1"));
|
or.addOr(new StringParam("A1"));
|
||||||
for (int i = 0; i < 50; i++) {
|
for (int i = 0; i < 50; i++) {
|
||||||
or.addOr(new StringParam(StringUtils.leftPad("", 200, (char) ('A' + i))));
|
or.addOr(new StringParam(leftPad("", 200, (char) ('A' + i))));
|
||||||
}
|
}
|
||||||
map.add(Patient.SP_NAME, or);
|
map.add(Patient.SP_NAME, or);
|
||||||
IBundleProvider results = myPatientDao.search(map);
|
IBundleProvider results = myPatientDao.search(map);
|
||||||
|
@ -5130,7 +5131,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
|
||||||
or.addOr(new StringParam("A1"));
|
or.addOr(new StringParam("A1"));
|
||||||
or.addOr(new StringParam("A1"));
|
or.addOr(new StringParam("A1"));
|
||||||
for (int i = 0; i < 50; i++) {
|
for (int i = 0; i < 50; i++) {
|
||||||
or.addOr(new StringParam(StringUtils.leftPad("", 200, (char) ('A' + i))));
|
or.addOr(new StringParam(leftPad("", 200, (char) ('A' + i))));
|
||||||
}
|
}
|
||||||
map.add(Patient.SP_NAME, or);
|
map.add(Patient.SP_NAME, or);
|
||||||
results = myPatientDao.search(map);
|
results = myPatientDao.search(map);
|
||||||
|
@ -5154,7 +5155,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
|
||||||
.setCode("MR");
|
.setCode("MR");
|
||||||
IIdType id1 = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
|
IIdType id1 = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
runInTransaction(()->{
|
runInTransaction(() -> {
|
||||||
List<ResourceIndexedSearchParamToken> params = myResourceIndexedSearchParamTokenDao
|
List<ResourceIndexedSearchParamToken> params = myResourceIndexedSearchParamTokenDao
|
||||||
.findAll()
|
.findAll()
|
||||||
.stream()
|
.stream()
|
||||||
|
@ -5401,9 +5402,9 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
|
||||||
SearchParameterMap map = new SearchParameterMap();
|
SearchParameterMap map = new SearchParameterMap();
|
||||||
StringOrListParam or = new StringOrListParam();
|
StringOrListParam or = new StringOrListParam();
|
||||||
or.addOr(new StringParam("A1"));
|
or.addOr(new StringParam("A1"));
|
||||||
or.addOr(new StringParam(StringUtils.leftPad("", 200, 'A')));
|
or.addOr(new StringParam(leftPad("", 200, 'A')));
|
||||||
or.addOr(new StringParam(StringUtils.leftPad("", 200, 'B')));
|
or.addOr(new StringParam(leftPad("", 200, 'B')));
|
||||||
or.addOr(new StringParam(StringUtils.leftPad("", 200, 'C')));
|
or.addOr(new StringParam(leftPad("", 200, 'C')));
|
||||||
map.add(Patient.SP_NAME, or);
|
map.add(Patient.SP_NAME, or);
|
||||||
IBundleProvider results = myPatientDao.search(map);
|
IBundleProvider results = myPatientDao.search(map);
|
||||||
assertEquals(1, results.getResources(0, 10).size());
|
assertEquals(1, results.getResources(0, 10).size());
|
||||||
|
@ -5412,9 +5413,9 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
|
||||||
map = new SearchParameterMap();
|
map = new SearchParameterMap();
|
||||||
or = new StringOrListParam();
|
or = new StringOrListParam();
|
||||||
or.addOr(new StringParam("A1"));
|
or.addOr(new StringParam("A1"));
|
||||||
or.addOr(new StringParam(StringUtils.leftPad("", 200, 'A')));
|
or.addOr(new StringParam(leftPad("", 200, 'A')));
|
||||||
or.addOr(new StringParam(StringUtils.leftPad("", 200, 'B')));
|
or.addOr(new StringParam(leftPad("", 200, 'B')));
|
||||||
or.addOr(new StringParam(StringUtils.leftPad("", 200, 'C')));
|
or.addOr(new StringParam(leftPad("", 200, 'C')));
|
||||||
map.add(Patient.SP_NAME, or);
|
map.add(Patient.SP_NAME, or);
|
||||||
results = myPatientDao.search(map);
|
results = myPatientDao.search(map);
|
||||||
assertEquals(1, results.getResources(0, 10).size());
|
assertEquals(1, results.getResources(0, 10).size());
|
||||||
|
@ -5552,6 +5553,68 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidInclude() {
|
||||||
|
|
||||||
|
// Empty is ignored (should not fail)
|
||||||
|
{
|
||||||
|
SearchParameterMap map = new SearchParameterMap()
|
||||||
|
.addInclude(new Include(""));
|
||||||
|
assertEquals(0, myPatientDao.search(map, mySrd).sizeOrThrowNpe());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Very long
|
||||||
|
String longString = leftPad("", 10000, 'A');
|
||||||
|
try {
|
||||||
|
SearchParameterMap map = new SearchParameterMap()
|
||||||
|
.addInclude(new Include("Patient:" + longString));
|
||||||
|
myPatientDao.search(map, mySrd);
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("Invalid _include parameter value: \"Patient:" + longString + "\". Unknown search parameter \"" + longString + "\" for resource type \"Patient\". Valid search parameters for this search are: [general-practitioner, link, organization]", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid
|
||||||
|
try {
|
||||||
|
SearchParameterMap map = new SearchParameterMap()
|
||||||
|
.addInclude(new Include(":"));
|
||||||
|
myPatientDao.search(map, mySrd);
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("Invalid _include parameter value: \":\". ", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown resource
|
||||||
|
try {
|
||||||
|
SearchParameterMap map = new SearchParameterMap()
|
||||||
|
.addInclude(new Include("Foo:patient"));
|
||||||
|
myPatientDao.search(map, mySrd);
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("Invalid _include parameter value: \"Foo:patient\". Invalid/unsupported resource type: \"Foo\"", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown param
|
||||||
|
try {
|
||||||
|
SearchParameterMap map = new SearchParameterMap()
|
||||||
|
.addInclude(new Include("Patient:foo"));
|
||||||
|
myPatientDao.search(map, mySrd);
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("Invalid _include parameter value: \"Patient:foo\". Unknown search parameter \"foo\" for resource type \"Patient\". Valid search parameters for this search are: [general-practitioner, link, organization]", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown target type
|
||||||
|
try {
|
||||||
|
SearchParameterMap map = new SearchParameterMap()
|
||||||
|
.addInclude(new Include("Patient:organization:Foo"));
|
||||||
|
myPatientDao.search(map, mySrd);
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("Invalid _include parameter value: \"Patient:organization:Foo\". Invalid/unsupported resource type: \"Foo\"", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String toStringMultiline(List<?> theResults) {
|
private String toStringMultiline(List<?> theResults) {
|
||||||
StringBuilder b = new StringBuilder();
|
StringBuilder b = new StringBuilder();
|
||||||
for (Object next : theResults) {
|
for (Object next : theResults) {
|
||||||
|
|
|
@ -138,7 +138,7 @@ public interface IBundleProvider {
|
||||||
/**
|
/**
|
||||||
* Get all resources
|
* Get all resources
|
||||||
*
|
*
|
||||||
* @return getResources(0, this.size ()). Return an empty list if size() is zero.
|
* @return <code>getResources(0, this.size())</code>. Return an empty list if <code>this.size()</code> is zero.
|
||||||
* @throws ConfigurationException if size() is null
|
* @throws ConfigurationException if size() is null
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
|
|
@ -508,21 +508,4 @@ public class IncludeTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
|
|
||||||
Organization org = new Organization();
|
|
||||||
org.setId("Organization/65546");
|
|
||||||
org.getNameElement().setValue("Contained Test Organization");
|
|
||||||
|
|
||||||
Patient patient = new Patient();
|
|
||||||
patient.setId("Patient/1333");
|
|
||||||
patient.addIdentifier().setSystem("urn:mrns").setValue("253345");
|
|
||||||
patient.getManagingOrganization().setResource(patient);
|
|
||||||
|
|
||||||
System.out.println(FhirContext.forR4().newXmlParser().setPrettyPrint(true).encodeResourceToString(patient));
|
|
||||||
|
|
||||||
patient.getManagingOrganization().getReference();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue