Handle invalid chains on custom search params (#1553)
* Handle invalid chains on custom search params * Add some more tests * One more test fix
This commit is contained in:
parent
0af79b84d9
commit
73961072a6
|
@ -1,5 +1,12 @@
|
||||||
package ca.uhn.fhir.rest.api;
|
package ca.uhn.fhir.rest.api;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.model.api.IQueryParameterOr;
|
||||||
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -22,13 +29,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.StringTokenizer;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterOr;
|
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
|
||||||
|
|
||||||
public class QualifiedParamList extends ArrayList<String> {
|
public class QualifiedParamList extends ArrayList<String> {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
@ -108,6 +108,13 @@ public class QualifiedParamList extends ArrayList<String> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If no value was found, at least add that empty string as a value. It should get ignored later, but at
|
||||||
|
// least this lets us give a sensible error message if the parameter name was bad. See
|
||||||
|
// ResourceProviderR4Test#testParameterWithNoValueThrowsError_InvalidChainOnCustomSearch for an example
|
||||||
|
if (retVal.size() == 0) {
|
||||||
|
retVal.add("");
|
||||||
|
}
|
||||||
|
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1326,13 +1326,11 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
RuntimeSearchParam paramDef = mySearchParamRegistry.getSearchParamByName(resourceDef, qualifiedParamName.getParamName());
|
RuntimeSearchParam paramDef = mySearchParamRegistry.getSearchParamByName(resourceDef, qualifiedParamName.getParamName());
|
||||||
|
|
||||||
for (String nextValue : theSource.get(nextParamName)) {
|
for (String nextValue : theSource.get(nextParamName)) {
|
||||||
if (isNotBlank(nextValue)) {
|
|
||||||
QualifiedParamList qualifiedParam = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(qualifiedParamName.getWholeQualifier(), nextValue);
|
QualifiedParamList qualifiedParam = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(qualifiedParamName.getWholeQualifier(), nextValue);
|
||||||
List<QualifiedParamList> paramList = Collections.singletonList(qualifiedParam);
|
List<QualifiedParamList> paramList = Collections.singletonList(qualifiedParam);
|
||||||
IQueryParameterAnd<?> parsedParam = ParameterUtil.parseQueryParams(getContext(), paramDef, nextParamName, paramList);
|
IQueryParameterAnd<?> parsedParam = ParameterUtil.parseQueryParams(getContext(), paramDef, nextParamName, paramList);
|
||||||
theTarget.add(qualifiedParamName.getParamName(), parsedParam);
|
theTarget.add(qualifiedParamName.getParamName(), parsedParam);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ public class JpaValidationSupportChainR5 extends ValidationSupportChain {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
public void flush() {
|
public void flush() {
|
||||||
myDefaultProfileValidationSupport.flush();
|
myDefaultProfileValidationSupport.flush();
|
||||||
}
|
}
|
||||||
|
@ -83,10 +84,4 @@ public class JpaValidationSupportChainR5 extends ValidationSupportChain {
|
||||||
addValidationSupport(new SnapshotGeneratingValidationSupport(myFhirContext, this));
|
addValidationSupport(new SnapshotGeneratingValidationSupport(myFhirContext, this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreDestroy
|
|
||||||
public void preDestroy() {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
package ca.uhn.fhir.jpa.dao.r4;
|
package ca.uhn.fhir.jpa.dao.r4;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.interceptor.api.Hook;
|
||||||
|
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||||
|
import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor;
|
||||||
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
|
||||||
|
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.model.api.Include;
|
import ca.uhn.fhir.model.api.Include;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
|
@ -10,11 +15,13 @@ import ca.uhn.fhir.rest.param.*;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.*;
|
import org.hl7.fhir.r4.model.*;
|
||||||
import org.hl7.fhir.r4.model.Appointment.AppointmentStatus;
|
import org.hl7.fhir.r4.model.Appointment.AppointmentStatus;
|
||||||
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
|
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.internal.util.collections.ListUtil;
|
import org.mockito.internal.util.collections.ListUtil;
|
||||||
import org.springframework.transaction.TransactionStatus;
|
import org.springframework.transaction.TransactionStatus;
|
||||||
import org.springframework.transaction.support.TransactionCallback;
|
import org.springframework.transaction.support.TransactionCallback;
|
||||||
|
@ -23,8 +30,11 @@ import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
|
import static org.hamcrest.Matchers.contains;
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test {
|
public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test {
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4SearchCustomSearchParamTest.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4SearchCustomSearchParamTest.class);
|
||||||
|
@ -181,7 +191,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
|
||||||
IBundleProvider outcome = myPatientDao.search(params);
|
IBundleProvider outcome = myPatientDao.search(params);
|
||||||
List<String> ids = toUnqualifiedVersionlessIdValues(outcome);
|
List<String> ids = toUnqualifiedVersionlessIdValues(outcome);
|
||||||
ourLog.info("IDS: " + ids);
|
ourLog.info("IDS: " + ids);
|
||||||
assertThat(ids, contains(pid.getValue()));
|
assertThat(ids, Matchers.contains(pid.getValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1326,6 +1336,39 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompositeWithInvalidTarget() {
|
||||||
|
SearchParameter sp = new SearchParameter();
|
||||||
|
sp.addBase("Patient");
|
||||||
|
sp.setCode("myDoctor");
|
||||||
|
sp.setType(Enumerations.SearchParamType.COMPOSITE);
|
||||||
|
sp.setTitle("My Doctor");
|
||||||
|
sp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
|
||||||
|
sp.addComponent()
|
||||||
|
.setDefinition("http://foo");
|
||||||
|
mySearchParameterDao.create(sp);
|
||||||
|
|
||||||
|
IAnonymousInterceptor interceptor = mock(IAnonymousInterceptor.class);
|
||||||
|
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.JPA_PERFTRACE_WARNING, interceptor);
|
||||||
|
|
||||||
|
try {
|
||||||
|
mySearchParamRegistry.forceRefresh();
|
||||||
|
|
||||||
|
ArgumentCaptor<HookParams> paramsCaptor = ArgumentCaptor.forClass(HookParams.class);
|
||||||
|
verify(interceptor, times(1)).invoke(any(), paramsCaptor.capture());
|
||||||
|
|
||||||
|
StorageProcessingMessage msg = paramsCaptor.getValue().get(StorageProcessingMessage.class);
|
||||||
|
assertThat(msg.getMessage(), containsString("refers to unknown component foo, ignoring this parameter"));
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
myInterceptorRegistry.unregisterInterceptor(interceptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
public static void afterClassClearContext() {
|
public static void afterClassClearContext() {
|
||||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||||
|
|
|
@ -116,6 +116,48 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
|
||||||
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
|
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParameterWithNoValueThrowsError_InvalidChainOnCustomSearch() throws IOException {
|
||||||
|
SearchParameter searchParameter = new SearchParameter();
|
||||||
|
searchParameter.addBase("BodySite").addBase("Procedure");
|
||||||
|
searchParameter.setCode("focalAccess");
|
||||||
|
searchParameter.setType(Enumerations.SearchParamType.REFERENCE);
|
||||||
|
searchParameter.setExpression("Procedure.extension('Procedure#focalAccess')");
|
||||||
|
searchParameter.setXpathUsage(SearchParameter.XPathUsageType.NORMAL);
|
||||||
|
searchParameter.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
||||||
|
ourClient.create().resource(searchParameter).execute();
|
||||||
|
|
||||||
|
mySearchParamRegistry.forceRefresh();
|
||||||
|
|
||||||
|
HttpGet get = new HttpGet(ourServerBase + "/Procedure?focalAccess.a%20ne%20e");
|
||||||
|
try (CloseableHttpResponse resp = ourHttpClient.execute(get)) {
|
||||||
|
String output = IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8);
|
||||||
|
assertThat(output, containsString("Invalid parameter chain: focalAccess.a ne e"));
|
||||||
|
assertEquals(400, resp.getStatusLine().getStatusCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParameterWithNoValueThrowsError_InvalidRootParam() throws IOException {
|
||||||
|
SearchParameter searchParameter = new SearchParameter();
|
||||||
|
searchParameter.addBase("BodySite").addBase("Procedure");
|
||||||
|
searchParameter.setCode("focalAccess");
|
||||||
|
searchParameter.setType(Enumerations.SearchParamType.REFERENCE);
|
||||||
|
searchParameter.setExpression("Procedure.extension('Procedure#focalAccess')");
|
||||||
|
searchParameter.setXpathUsage(SearchParameter.XPathUsageType.NORMAL);
|
||||||
|
searchParameter.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
||||||
|
ourClient.create().resource(searchParameter).execute();
|
||||||
|
|
||||||
|
mySearchParamRegistry.forceRefresh();
|
||||||
|
|
||||||
|
HttpGet get = new HttpGet(ourServerBase + "/Procedure?a");
|
||||||
|
try (CloseableHttpResponse resp = ourHttpClient.execute(get)) {
|
||||||
|
String output = IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8);
|
||||||
|
assertThat(output, containsString("Unknown search parameter "a""));
|
||||||
|
assertEquals(400, resp.getStatusLine().getStatusCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearchForTokenValueOnlyUsesValueHash() {
|
public void testSearchForTokenValueOnlyUsesValueHash() {
|
||||||
|
|
||||||
|
|
|
@ -239,6 +239,24 @@ public class ResourceReindexingSvcImplTest extends BaseJpaTest {
|
||||||
verifyNoMoreInteractions(myReindexJobDao);
|
verifyNoMoreInteractions(myReindexJobDao);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReindexThrowsError() {
|
||||||
|
mockNothingToExpunge();
|
||||||
|
mockSingleReindexingJob("Patient");
|
||||||
|
List<Long> values = Arrays.asList(0L, 1L, 2L, 3L);
|
||||||
|
when(myResourceTableDao.findIdsOfResourcesWithinUpdatedRangeOrderedFromOldest(myPageRequestCaptor.capture(), myTypeCaptor.capture(), myLowCaptor.capture(), myHighCaptor.capture())).thenReturn(new SliceImpl<>(values));
|
||||||
|
when(myResourceTableDao.findById(anyLong())).thenThrow(new NullPointerException("A MESSAGE"));
|
||||||
|
|
||||||
|
int count = mySvc.forceReindexingPass();
|
||||||
|
assertEquals(0, count);
|
||||||
|
|
||||||
|
// Make sure we didn't do anything unexpected
|
||||||
|
verify(myReindexJobDao, times(1)).findAll(any(), eq(false));
|
||||||
|
verify(myReindexJobDao, times(1)).findAll(any(), eq(true));
|
||||||
|
verify(myReindexJobDao, times(1)).setSuspendedUntil(any());
|
||||||
|
verifyNoMoreInteractions(myReindexJobDao);
|
||||||
|
}
|
||||||
|
|
||||||
private void mockWhenResourceTableFindById(long[] theUpdatedTimes, String[] theResourceTypes) {
|
private void mockWhenResourceTableFindById(long[] theUpdatedTimes, String[] theResourceTypes) {
|
||||||
when(myResourceTableDao.findById(any())).thenAnswer(t -> {
|
when(myResourceTableDao.findById(any())).thenAnswer(t -> {
|
||||||
ResourceTable retVal = new ResourceTable();
|
ResourceTable retVal = new ResourceTable();
|
||||||
|
|
|
@ -149,8 +149,7 @@ public final class ResourceIndexedSearchParams {
|
||||||
* @param theResourceType E.g. <code>Patient
|
* @param theResourceType E.g. <code>Patient
|
||||||
* @param thePartsChoices E.g. <code>[[gender=male], [name=SMITH, name=JOHN]]</code>
|
* @param thePartsChoices E.g. <code>[[gender=male], [name=SMITH, name=JOHN]]</code>
|
||||||
*/
|
*/
|
||||||
public static Set<String> extractCompositeStringUniquesValueChains(String
|
public static Set<String> extractCompositeStringUniquesValueChains(String theResourceType, List<List<String>> thePartsChoices) {
|
||||||
theResourceType, List<List<String>> thePartsChoices) {
|
|
||||||
|
|
||||||
for (List<String> next : thePartsChoices) {
|
for (List<String> next : thePartsChoices) {
|
||||||
next.removeIf(StringUtils::isBlank);
|
next.removeIf(StringUtils::isBlank);
|
||||||
|
|
|
@ -3,14 +3,17 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
|
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
|
||||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import com.google.common.collect.Lists;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||||
|
import static org.hamcrest.Matchers.empty;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class ResourceIndexedSearchParamsTest {
|
public class ResourceIndexedSearchParamsTest {
|
||||||
|
@ -79,4 +82,30 @@ public class ResourceIndexedSearchParamsTest {
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtractCompositeStringUniquesValueChains() {
|
||||||
|
List<List<String>> partsChoices;
|
||||||
|
Set<String> values;
|
||||||
|
|
||||||
|
partsChoices = Lists.newArrayList(
|
||||||
|
Lists.newArrayList("gender=male"),
|
||||||
|
Lists.newArrayList("name=SMITH", "name=JOHN")
|
||||||
|
);
|
||||||
|
values = ResourceIndexedSearchParams.extractCompositeStringUniquesValueChains("Patient", partsChoices);
|
||||||
|
assertThat(values.toString(), values, containsInAnyOrder("Patient?gender=male&name=JOHN","Patient?gender=male&name=SMITH"));
|
||||||
|
|
||||||
|
partsChoices = Lists.newArrayList(
|
||||||
|
Lists.newArrayList("gender=male", ""),
|
||||||
|
Lists.newArrayList("name=SMITH", "name=JOHN", "")
|
||||||
|
);
|
||||||
|
values = ResourceIndexedSearchParams.extractCompositeStringUniquesValueChains("Patient", partsChoices);
|
||||||
|
assertThat(values.toString(), values, containsInAnyOrder("Patient?gender=male&name=JOHN","Patient?gender=male&name=SMITH"));
|
||||||
|
|
||||||
|
partsChoices = Lists.newArrayList(
|
||||||
|
);
|
||||||
|
values = ResourceIndexedSearchParams.extractCompositeStringUniquesValueChains("Patient", partsChoices);
|
||||||
|
assertThat(values.toString(), values, empty());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,6 +83,12 @@ public class SubscriptionLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void acquireSemaphoreForUnitTest() throws InterruptedException {
|
||||||
|
mySyncSubscriptionsSemaphore.acquire();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void registerScheduledJob() {
|
public void registerScheduledJob() {
|
||||||
ScheduledJobDefinition jobDetail = new ScheduledJobDefinition();
|
ScheduledJobDefinition jobDetail = new ScheduledJobDefinition();
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package ca.uhn.fhir.jpa.subscription.module.standalone;
|
package ca.uhn.fhir.jpa.subscription.module.cache;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.config.MockFhirClientSubscriptionProvider;
|
import ca.uhn.fhir.jpa.subscription.module.config.MockFhirClientSubscriptionProvider;
|
||||||
|
import ca.uhn.fhir.jpa.subscription.module.standalone.BaseBlockingQueueSubscribableChannelDstu3Test;
|
||||||
import org.hl7.fhir.dstu3.model.Subscription;
|
import org.hl7.fhir.dstu3.model.Subscription;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
@ -43,4 +45,13 @@ public class SubscriptionLoaderTest extends BaseBlockingQueueSubscribableChannel
|
||||||
mySubscriptionActivatedPost.awaitExpected();
|
mySubscriptionActivatedPost.awaitExpected();
|
||||||
assertEquals(0, myMockFhirClientSubscriptionProvider.getFailCount());
|
assertEquals(0, myMockFhirClientSubscriptionProvider.getFailCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipleThreadsDontBlock() throws InterruptedException {
|
||||||
|
SubscriptionLoader svc = new SubscriptionLoader();
|
||||||
|
svc.acquireSemaphoreForUnitTest();
|
||||||
|
svc.syncSubscriptions();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -2,6 +2,7 @@ package ca.uhn.fhir.rest.server.method;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
|
@ -103,7 +104,8 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
|
||||||
// type let's grab it
|
// type let's grab it
|
||||||
if (!Modifier.isAbstract(theReturnResourceType.getModifiers()) && !Modifier.isInterface(theReturnResourceType.getModifiers())) {
|
if (!Modifier.isAbstract(theReturnResourceType.getModifiers()) && !Modifier.isInterface(theReturnResourceType.getModifiers())) {
|
||||||
Class<? extends IBaseResource> resourceType = (Class<? extends IResource>) theReturnResourceType;
|
Class<? extends IBaseResource> resourceType = (Class<? extends IResource>) theReturnResourceType;
|
||||||
myResourceName = theContext.getResourceDefinition(resourceType).getName();
|
RuntimeResourceDefinition resourceDefinition = theContext.getResourceDefinition(resourceType);
|
||||||
|
myResourceName = resourceDefinition.getName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,170 @@
|
||||||
|
package ca.uhn.fhir.rest.server.method;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
|
import ca.uhn.fhir.model.api.annotation.ResourceDef;
|
||||||
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Read;
|
||||||
|
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
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 org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class ReadMethodBindingTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private FhirContext myCtx;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RequestDetails myRequestDetails;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RuntimeResourceDefinition definition;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIncomingServerRequestMatchesMethod_Read() throws NoSuchMethodException {
|
||||||
|
|
||||||
|
class MyProvider {
|
||||||
|
@Read(version = false)
|
||||||
|
public IBaseResource read(@IdParam IIdType theIdType) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when(myCtx.getResourceDefinition(any(Class.class))).thenReturn(definition);
|
||||||
|
when(definition.getName()).thenReturn("Patient");
|
||||||
|
when(myRequestDetails.getResourceName()).thenReturn("Patient");
|
||||||
|
when(myRequestDetails.getRequestType()).thenReturn(RequestTypeEnum.GET);
|
||||||
|
|
||||||
|
// Read
|
||||||
|
ReadMethodBinding binding = createBinding(new MyProvider());
|
||||||
|
when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123"));
|
||||||
|
assertTrue(binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||||
|
|
||||||
|
// VRead
|
||||||
|
when(myRequestDetails.getResourceName()).thenReturn("Patient");
|
||||||
|
when(myRequestDetails.getOperation()).thenReturn("_history");
|
||||||
|
assertFalse(binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIncomingServerRequestMatchesMethod_VRead() throws NoSuchMethodException {
|
||||||
|
|
||||||
|
class MyProvider {
|
||||||
|
@Read(version = true)
|
||||||
|
public IBaseResource read(@IdParam IIdType theIdType) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when(myCtx.getResourceDefinition(any(Class.class))).thenReturn(definition);
|
||||||
|
when(definition.getName()).thenReturn("Patient");
|
||||||
|
when(myRequestDetails.getResourceName()).thenReturn("Patient");
|
||||||
|
when(myRequestDetails.getRequestType()).thenReturn(RequestTypeEnum.GET);
|
||||||
|
|
||||||
|
// Read - wrong resource type
|
||||||
|
ReadMethodBinding binding = createBinding(new MyProvider());
|
||||||
|
when(myRequestDetails.getResourceName()).thenReturn("Observation");
|
||||||
|
when(myRequestDetails.getId()).thenReturn(new IdDt("Observation/123"));
|
||||||
|
assertFalse(binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||||
|
|
||||||
|
// Read
|
||||||
|
when(myRequestDetails.getResourceName()).thenReturn("Patient");
|
||||||
|
when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123"));
|
||||||
|
assertTrue(binding.incomingServerRequestMatchesMethod(myRequestDetails));
|
||||||
|
|
||||||
|
// VRead
|
||||||
|
when(myRequestDetails.getId()).thenReturn(new IdDt("Patient/123/_history/123"));
|
||||||
|
when(myRequestDetails.getOperation()).thenReturn("_history");
|
||||||
|
assertTrue(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));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadMethodBinding createBinding(Object theProvider) throws NoSuchMethodException {
|
||||||
|
Method method = theProvider.getClass().getMethod("read", IIdType.class);
|
||||||
|
return new ReadMethodBinding(MyPatient.class, method, myCtx, theProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ResourceDef(name = "Patient")
|
||||||
|
private static class MyPatient implements IBaseResource {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBaseMetaType getMeta() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IIdType getIdElement() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBaseResource setId(String theId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBaseResource setId(IIdType theId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FhirVersionEnum getStructureFhirVersionEnum() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasFormatComment() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getFormatCommentsPre() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getFormatCommentsPost() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getUserData(String theName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUserData(String theName, Object theValue) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -433,6 +433,12 @@
|
||||||
JavaScript dependency libraries. This reduces our Git repository size and
|
JavaScript dependency libraries. This reduces our Git repository size and
|
||||||
should make it easier to stay up-to-date.
|
should make it easier to stay up-to-date.
|
||||||
</action>
|
</action>
|
||||||
|
<action type="fix">
|
||||||
|
In the (fairly unlikely) circumstance that a JPA server was called with a parameter where the parameter name referenced
|
||||||
|
a custom search parameter with an invalid chain attached, and the value was missing entirely (e.g.
|
||||||
|
<![CDATA[<code>ProcedureRequest?someCustomParameter.BAD_NAME=</code>]]>, the server would ignore this
|
||||||
|
parameter instead of incorrectly returning an error. This has been corrected.
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="4.0.3" date="2019-09-03" description="Igloo (Point Release)">
|
<release version="4.0.3" date="2019-09-03" description="Igloo (Point Release)">
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
|
|
Loading…
Reference in New Issue