Add config options for default Prefer header and _total param on server
This commit is contained in:
parent
8c87c7c089
commit
5b8fee869e
|
@ -1305,9 +1305,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
|
|
||||||
changed = populateResourceIntoEntity(theRequest, theResource, theEntity, true);
|
changed = populateResourceIntoEntity(theRequest, theResource, theEntity, true);
|
||||||
|
|
||||||
// FIXME: remove
|
|
||||||
ourLog.info("** Updated setting to: " + new InstantType(theUpdateTime).getValueAsString());
|
|
||||||
|
|
||||||
theEntity.setUpdated(theUpdateTime);
|
theEntity.setUpdated(theUpdateTime);
|
||||||
if (theResource instanceof IResource) {
|
if (theResource instanceof IResource) {
|
||||||
theEntity.setLanguage(((IResource) theResource).getLanguage().getValue());
|
theEntity.setLanguage(((IResource) theResource).getLanguage().getValue());
|
||||||
|
@ -1322,11 +1319,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
|
|
||||||
changed = populateResourceIntoEntity(theRequest, theResource, theEntity, false);
|
changed = populateResourceIntoEntity(theRequest, theResource, theEntity, false);
|
||||||
|
|
||||||
// FIXME: remove
|
|
||||||
ourLog.info("** Updated setting to: " + new InstantType(theUpdateTime).getValueAsString());
|
|
||||||
|
|
||||||
theEntity.setUpdated(theUpdateTime);
|
theEntity.setUpdated(theUpdateTime);
|
||||||
// theEntity.setLanguage(theResource.getLanguage().getValue());
|
|
||||||
theEntity.setIndexStatus(null);
|
theEntity.setIndexStatus(null);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,7 +148,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
theResource.setId(UUID.randomUUID().toString());
|
theResource.setId(UUID.randomUUID().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: this is where one date is created
|
|
||||||
return doCreate(theResource, theIfNoneExist, thePerformIndexing, theUpdateTimestamp, theRequestDetails);
|
return doCreate(theResource, theIfNoneExist, thePerformIndexing, theUpdateTimestamp, theRequestDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
|
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
|
||||||
import ca.uhn.fhir.jpa.search.warm.WarmCacheEntry;
|
import ca.uhn.fhir.jpa.search.warm.WarmCacheEntry;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
|
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
|
||||||
|
import ca.uhn.fhir.rest.api.SearchTotalModeEnum;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
@ -25,9 +26,9 @@ import java.util.*;
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -98,6 +99,7 @@ public class DaoConfig {
|
||||||
private boolean myUniqueIndexesEnabled = true;
|
private boolean myUniqueIndexesEnabled = true;
|
||||||
private boolean myUniqueIndexesCheckedBeforeSave = true;
|
private boolean myUniqueIndexesCheckedBeforeSave = true;
|
||||||
private boolean myEnforceReferentialIntegrityOnWrite = true;
|
private boolean myEnforceReferentialIntegrityOnWrite = true;
|
||||||
|
private SearchTotalModeEnum myDefaultTotalMode = null;
|
||||||
private int myEverythingIncludesFetchPageSize = 50;
|
private int myEverythingIncludesFetchPageSize = 50;
|
||||||
/**
|
/**
|
||||||
* update setter javadoc if default changes
|
* update setter javadoc if default changes
|
||||||
|
@ -141,7 +143,6 @@ public class DaoConfig {
|
||||||
private boolean myDisableHashBasedSearches;
|
private boolean myDisableHashBasedSearches;
|
||||||
private boolean myEnableInMemorySubscriptionMatching = true;
|
private boolean myEnableInMemorySubscriptionMatching = true;
|
||||||
private ClientIdStrategyEnum myResourceClientIdStrategy = ClientIdStrategyEnum.ALPHANUMERIC;
|
private ClientIdStrategyEnum myResourceClientIdStrategy = ClientIdStrategyEnum.ALPHANUMERIC;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
|
@ -159,6 +160,30 @@ public class DaoConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a non-null value is supplied (default is <code>null</code>), a default
|
||||||
|
* for the <code>_total</code> parameter may be specified here. For example,
|
||||||
|
* setting this value to {@link SearchTotalModeEnum#ACCURATE} will force a
|
||||||
|
* count to always be calculated for all searches. This can have a performance impact
|
||||||
|
* since it means that a count query will always be performed, but this is desirable
|
||||||
|
* for some solutions.
|
||||||
|
*/
|
||||||
|
public SearchTotalModeEnum getDefaultTotalMode() {
|
||||||
|
return myDefaultTotalMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a non-null value is supplied (default is <code>null</code>), a default
|
||||||
|
* for the <code>_total</code> parameter may be specified here. For example,
|
||||||
|
* setting this value to {@link SearchTotalModeEnum#ACCURATE} will force a
|
||||||
|
* count to always be calculated for all searches. This can have a performance impact
|
||||||
|
* since it means that a count query will always be performed, but this is desirable
|
||||||
|
* for some solutions.
|
||||||
|
*/
|
||||||
|
public void setDefaultTotalMode(SearchTotalModeEnum theDefaultTotalMode) {
|
||||||
|
myDefaultTotalMode = theDefaultTotalMode;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a set of searches that should be kept "warm", meaning that
|
* Returns a set of searches that should be kept "warm", meaning that
|
||||||
* searches will periodically be performed in the background to
|
* searches will periodically be performed in the background to
|
||||||
|
@ -492,18 +517,6 @@ public class DaoConfig {
|
||||||
return myInterceptors;
|
return myInterceptors;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerInterceptor(IServerInterceptor theInterceptor) {
|
|
||||||
Validate.notNull(theInterceptor, "Interceptor can not be null");
|
|
||||||
if (!myInterceptors.contains(theInterceptor)) {
|
|
||||||
myInterceptors.add(theInterceptor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unregisterInterceptor(IServerInterceptor theInterceptor) {
|
|
||||||
Validate.notNull(theInterceptor, "Interceptor can not be null");
|
|
||||||
myInterceptors.remove(theInterceptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This may be used to optionally register server interceptors directly against the DAOs.
|
* This may be used to optionally register server interceptors directly against the DAOs.
|
||||||
*/
|
*/
|
||||||
|
@ -521,6 +534,18 @@ public class DaoConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void registerInterceptor(IServerInterceptor theInterceptor) {
|
||||||
|
Validate.notNull(theInterceptor, "Interceptor can not be null");
|
||||||
|
if (!myInterceptors.contains(theInterceptor)) {
|
||||||
|
myInterceptors.add(theInterceptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterInterceptor(IServerInterceptor theInterceptor) {
|
||||||
|
Validate.notNull(theInterceptor, "Interceptor can not be null");
|
||||||
|
myInterceptors.remove(theInterceptor);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See {@link #setMaximumExpansionSize(int)}
|
* See {@link #setMaximumExpansionSize(int)}
|
||||||
*/
|
*/
|
||||||
|
@ -1477,7 +1502,6 @@ public class DaoConfig {
|
||||||
/**
|
/**
|
||||||
* This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted
|
* This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted
|
||||||
* to the server matching these types will be activated.
|
* to the server matching these types will be activated.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public DaoConfig addSupportedSubscriptionType(Subscription.SubscriptionChannelType theSubscriptionChannelType) {
|
public DaoConfig addSupportedSubscriptionType(Subscription.SubscriptionChannelType theSubscriptionChannelType) {
|
||||||
myModelConfig.addSupportedSubscriptionType(theSubscriptionChannelType);
|
myModelConfig.addSupportedSubscriptionType(theSubscriptionChannelType);
|
||||||
|
@ -1487,7 +1511,6 @@ public class DaoConfig {
|
||||||
/**
|
/**
|
||||||
* This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted
|
* This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted
|
||||||
* to the server matching these types will be activated.
|
* to the server matching these types will be activated.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public Set<Subscription.SubscriptionChannelType> getSupportedSubscriptionTypes() {
|
public Set<Subscription.SubscriptionChannelType> getSupportedSubscriptionTypes() {
|
||||||
return myModelConfig.getSupportedSubscriptionTypes();
|
return myModelConfig.getSupportedSubscriptionTypes();
|
||||||
|
@ -1515,7 +1538,6 @@ public class DaoConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public enum IndexEnabledEnum {
|
public enum IndexEnabledEnum {
|
||||||
ENABLED,
|
ENABLED,
|
||||||
DISABLED
|
DISABLED
|
||||||
|
|
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.search;
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -103,9 +103,19 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
private IPagingProvider myPagingProvider;
|
private IPagingProvider myPagingProvider;
|
||||||
|
|
||||||
private int mySyncSize = DEFAULT_SYNC_SIZE;
|
private int mySyncSize = DEFAULT_SYNC_SIZE;
|
||||||
/** Set in {@link #start()} */
|
/**
|
||||||
|
* Set in {@link #start()}
|
||||||
|
*/
|
||||||
private boolean myCustomIsolationSupported;
|
private boolean myCustomIsolationSupported;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
public SearchCoordinatorSvcImpl() {
|
||||||
|
CustomizableThreadFactory threadFactory = new CustomizableThreadFactory("search_coord_");
|
||||||
|
myExecutor = Executors.newCachedThreadPool(threadFactory);
|
||||||
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void start() {
|
public void start() {
|
||||||
if (myManagedTxManager instanceof JpaTransactionManager) {
|
if (myManagedTxManager instanceof JpaTransactionManager) {
|
||||||
|
@ -119,14 +129,6 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
public SearchCoordinatorSvcImpl() {
|
|
||||||
CustomizableThreadFactory threadFactory = new CustomizableThreadFactory("search_coord_");
|
|
||||||
myExecutor = Executors.newCachedThreadPool(threadFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cancelAllActiveSearches() {
|
public void cancelAllActiveSearches() {
|
||||||
for (BaseTask next : myIdToSearchTask.values()) {
|
for (BaseTask next : myIdToSearchTask.values()) {
|
||||||
|
@ -466,6 +468,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
private List<Long> myPreviouslyAddedResourcePids;
|
private List<Long> myPreviouslyAddedResourcePids;
|
||||||
private Integer myMaxResultsToFetch;
|
private Integer myMaxResultsToFetch;
|
||||||
private int myCountFetchedDuringThisPass;
|
private int myCountFetchedDuringThisPass;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
|
@ -763,7 +766,10 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
* before doing anything else.
|
* before doing anything else.
|
||||||
*/
|
*/
|
||||||
boolean wantOnlyCount = SummaryEnum.COUNT.equals(myParams.getSummaryMode());
|
boolean wantOnlyCount = SummaryEnum.COUNT.equals(myParams.getSummaryMode());
|
||||||
boolean wantCount = wantOnlyCount || SearchTotalModeEnum.ACCURATE.equals(myParams.getSearchTotalMode());
|
boolean wantCount =
|
||||||
|
wantOnlyCount ||
|
||||||
|
SearchTotalModeEnum.ACCURATE.equals(myParams.getSearchTotalMode()) ||
|
||||||
|
(myParams.getSearchTotalMode() == null && SearchTotalModeEnum.ACCURATE.equals(myDaoConfig.getDefaultTotalMode()));
|
||||||
if (wantCount) {
|
if (wantCount) {
|
||||||
ourLog.trace("Performing count");
|
ourLog.trace("Performing count");
|
||||||
ISearchBuilder sb = newSearchBuilder();
|
ISearchBuilder sb = newSearchBuilder();
|
||||||
|
|
|
@ -61,7 +61,7 @@ public class DaoSubscriptionMatcher implements ISubscriptionMatcher {
|
||||||
|
|
||||||
ourLog.debug("Subscription check found {} results for query: {}", results.size(), criteria);
|
ourLog.debug("Subscription check found {} results for query: {}", results.size(), criteria);
|
||||||
|
|
||||||
return new SubscriptionMatchResult(results.size() > 0);
|
return new SubscriptionMatchResult(results.size() > 0, "DATABASE");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -8,6 +8,7 @@ import java.util.List;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
||||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
|
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
|
||||||
import org.hl7.fhir.r4.model.*;
|
import org.hl7.fhir.r4.model.*;
|
||||||
import org.hl7.fhir.r4.model.Observation.ObservationStatus;
|
import org.hl7.fhir.r4.model.Observation.ObservationStatus;
|
||||||
|
@ -341,7 +342,7 @@ public class FhirResourceDaoR4SearchFtTest extends BaseJpaR4Test {
|
||||||
request = mock(HttpServletRequest.class);
|
request = mock(HttpServletRequest.class);
|
||||||
StringAndListParam param;
|
StringAndListParam param;
|
||||||
|
|
||||||
ourLog.info("Pt1:{} Pt2:{} Obs1:{} Obs2:{} Obs3:{}", new Object[] {ptId1.getIdPart(), ptId2.getIdPart(), obsId1.getIdPart(), obsId2.getIdPart(), obsId3.getIdPart()});
|
ourLog.info("Pt1:{} Pt2:{} Obs1:{} Obs2:{} Obs3:{}", ptId1.getIdPart(), ptId2.getIdPart(), obsId1.getIdPart(), obsId2.getIdPart(), obsId3.getIdPart());
|
||||||
|
|
||||||
param = new StringAndListParam();
|
param = new StringAndListParam();
|
||||||
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
|
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
|
||||||
|
@ -433,7 +434,7 @@ public class FhirResourceDaoR4SearchFtTest extends BaseJpaR4Test {
|
||||||
request = mock(HttpServletRequest.class);
|
request = mock(HttpServletRequest.class);
|
||||||
StringAndListParam param;
|
StringAndListParam param;
|
||||||
|
|
||||||
ourLog.info("Pt1:{} Pt2:{} Obs1:{} Obs2:{} Obs3:{}", new Object[] {ptId1.getIdPart(), ptId2.getIdPart(), obsId1.getIdPart(), obsId2.getIdPart(), obsId3.getIdPart()});
|
ourLog.info("Pt1:{} Pt2:{} Obs1:{} Obs2:{} Obs3:{}", ptId1.getIdPart(), ptId2.getIdPart(), obsId1.getIdPart(), obsId2.getIdPart(), obsId3.getIdPart());
|
||||||
|
|
||||||
param = new StringAndListParam();
|
param = new StringAndListParam();
|
||||||
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
|
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
|
||||||
|
@ -485,7 +486,7 @@ public class FhirResourceDaoR4SearchFtTest extends BaseJpaR4Test {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testSearchDontReindexForUpdateWithIndexDisabled() {
|
public void testSearchDontReindexForUpdateWithIndexDisabled() {
|
||||||
BaseHapiFhirResourceDao.setDisableIncrementOnUpdateForUnitTest(true);
|
BaseHapiFhirDao.setDisableIncrementOnUpdateForUnitTest(true);
|
||||||
Patient patient;
|
Patient patient;
|
||||||
SearchParameterMap map;
|
SearchParameterMap map;
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ public class ResourceProviderSummaryModeR4Test extends BaseResourceProviderR4Tes
|
||||||
mySearchCoordinatorSvcRaw.setLoadingThrottleForUnitTests(null);
|
mySearchCoordinatorSvcRaw.setLoadingThrottleForUnitTests(null);
|
||||||
mySearchCoordinatorSvcRaw.setSyncSizeForUnitTests(SearchCoordinatorSvcImpl.DEFAULT_SYNC_SIZE);
|
mySearchCoordinatorSvcRaw.setSyncSizeForUnitTests(SearchCoordinatorSvcImpl.DEFAULT_SYNC_SIZE);
|
||||||
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
|
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
|
||||||
|
myDaoConfig.setDefaultTotalMode(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -93,6 +94,24 @@ public class ResourceProviderSummaryModeR4Test extends BaseResourceProviderR4Tes
|
||||||
assertEquals(10, outcome.getEntry().size());
|
assertEquals(10, outcome.getEntry().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count and data via config - Should include both a count and the data portions of results
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSearchWithTotalAccurateSpecifiedAsDefault() {
|
||||||
|
myDaoConfig.setDefaultTotalMode(SearchTotalModeEnum.ACCURATE);
|
||||||
|
|
||||||
|
Bundle outcome = ourClient
|
||||||
|
.search()
|
||||||
|
.forResource(Patient.class)
|
||||||
|
.where(Patient.ACTIVE.exactly().code("true"))
|
||||||
|
.returnBundle(Bundle.class)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals(new Integer(104), outcome.getTotalElement().getValue());
|
||||||
|
assertEquals(10, outcome.getEntry().size());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No summary mode - Should return the first page of results but not
|
* No summary mode - Should return the first page of results but not
|
||||||
* have the total available yet
|
* have the total available yet
|
||||||
|
@ -110,6 +129,26 @@ public class ResourceProviderSummaryModeR4Test extends BaseResourceProviderR4Tes
|
||||||
assertEquals(10, outcome.getEntry().size());
|
assertEquals(10, outcome.getEntry().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No summary mode - Should return the first page of results but not
|
||||||
|
* have the total available yet
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSearchTotalNoneOverridingDefault() {
|
||||||
|
myDaoConfig.setDefaultTotalMode(SearchTotalModeEnum.ACCURATE);
|
||||||
|
|
||||||
|
Bundle outcome = ourClient
|
||||||
|
.search()
|
||||||
|
.forResource(Patient.class)
|
||||||
|
.where(Patient.ACTIVE.exactly().code("true"))
|
||||||
|
.totalMode(SearchTotalModeEnum.NONE)
|
||||||
|
.returnBundle(Bundle.class)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals(null, outcome.getTotalElement().getValue());
|
||||||
|
assertEquals(10, outcome.getEntry().size());
|
||||||
|
}
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
public static void afterClassClearContext() {
|
public static void afterClassClearContext() {
|
||||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||||
|
|
|
@ -176,6 +176,27 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRepeatedDeliveries() throws Exception {
|
||||||
|
String payload = "application/fhir+json";
|
||||||
|
|
||||||
|
String code = "1000000050";
|
||||||
|
String criteria1 = "Observation?";
|
||||||
|
|
||||||
|
createSubscription(criteria1, payload);
|
||||||
|
waitForActivatedSubscriptionCount(1);
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
Observation observation = new Observation();
|
||||||
|
observation.getIdentifierFirstRep().setSystem("foo").setValue("ID" + i);
|
||||||
|
observation.getCode().addCoding().setCode(code).setSystem("SNOMED-CT");
|
||||||
|
observation.setStatus(Observation.ObservationStatus.FINAL);
|
||||||
|
myObservationDao.create(observation);
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForSize(100, ourUpdatedObservations);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActiveSubscriptionShouldntReActivate() throws Exception {
|
public void testActiveSubscriptionShouldntReActivate() throws Exception {
|
||||||
|
|
|
@ -42,6 +42,7 @@ import java.util.function.Predicate;
|
||||||
@Service
|
@Service
|
||||||
public class CriteriaResourceMatcher {
|
public class CriteriaResourceMatcher {
|
||||||
|
|
||||||
|
public static final String CRITERIA = "CRITERIA";
|
||||||
@Autowired
|
@Autowired
|
||||||
private MatchUrlService myMatchUrlService;
|
private MatchUrlService myMatchUrlService;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -52,7 +53,7 @@ public class CriteriaResourceMatcher {
|
||||||
try {
|
try {
|
||||||
searchParameterMap = myMatchUrlService.translateMatchUrl(theCriteria, theResourceDefinition);
|
searchParameterMap = myMatchUrlService.translateMatchUrl(theCriteria, theResourceDefinition);
|
||||||
} catch (UnsupportedOperationException e) {
|
} catch (UnsupportedOperationException e) {
|
||||||
return new SubscriptionMatchResult(theCriteria);
|
return new SubscriptionMatchResult(theCriteria, CRITERIA);
|
||||||
}
|
}
|
||||||
searchParameterMap.clean();
|
searchParameterMap.clean();
|
||||||
if (searchParameterMap.getLastUpdated() != null) {
|
if (searchParameterMap.getLastUpdated() != null) {
|
||||||
|
@ -67,13 +68,13 @@ public class CriteriaResourceMatcher {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return SubscriptionMatchResult.MATCH;
|
return new SubscriptionMatchResult(true, CRITERIA);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method is modelled from SearchBuilder.searchForIdsWithAndOr()
|
// This method is modelled from SearchBuilder.searchForIdsWithAndOr()
|
||||||
private SubscriptionMatchResult matchIdsWithAndOr(String theParamName, List<List<? extends IQueryParameterType>> theAndOrParams, RuntimeResourceDefinition theResourceDefinition, ResourceIndexedSearchParams theSearchParams) {
|
private SubscriptionMatchResult matchIdsWithAndOr(String theParamName, List<List<? extends IQueryParameterType>> theAndOrParams, RuntimeResourceDefinition theResourceDefinition, ResourceIndexedSearchParams theSearchParams) {
|
||||||
if (theAndOrParams.isEmpty()) {
|
if (theAndOrParams.isEmpty()) {
|
||||||
return SubscriptionMatchResult.MATCH;
|
return new SubscriptionMatchResult(true, CRITERIA);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasQualifiers(theAndOrParams)) {
|
if (hasQualifiers(theAndOrParams)) {
|
||||||
|
@ -91,19 +92,19 @@ public class CriteriaResourceMatcher {
|
||||||
}
|
}
|
||||||
if (theParamName.equals(IAnyResource.SP_RES_ID)) {
|
if (theParamName.equals(IAnyResource.SP_RES_ID)) {
|
||||||
|
|
||||||
return new SubscriptionMatchResult(theParamName);
|
return new SubscriptionMatchResult(theParamName, CRITERIA);
|
||||||
|
|
||||||
} else if (theParamName.equals(IAnyResource.SP_RES_LANGUAGE)) {
|
} else if (theParamName.equals(IAnyResource.SP_RES_LANGUAGE)) {
|
||||||
|
|
||||||
return new SubscriptionMatchResult(theParamName);
|
return new SubscriptionMatchResult(theParamName, CRITERIA);
|
||||||
|
|
||||||
} else if (theParamName.equals(Constants.PARAM_HAS)) {
|
} else if (theParamName.equals(Constants.PARAM_HAS)) {
|
||||||
|
|
||||||
return new SubscriptionMatchResult(theParamName);
|
return new SubscriptionMatchResult(theParamName, CRITERIA);
|
||||||
|
|
||||||
} else if (theParamName.equals(Constants.PARAM_TAG) || theParamName.equals(Constants.PARAM_PROFILE) || theParamName.equals(Constants.PARAM_SECURITY)) {
|
} else if (theParamName.equals(Constants.PARAM_TAG) || theParamName.equals(Constants.PARAM_PROFILE) || theParamName.equals(Constants.PARAM_SECURITY)) {
|
||||||
|
|
||||||
return new SubscriptionMatchResult(theParamName);
|
return new SubscriptionMatchResult(theParamName, CRITERIA);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
@ -123,16 +124,16 @@ public class CriteriaResourceMatcher {
|
||||||
case URI:
|
case URI:
|
||||||
case DATE:
|
case DATE:
|
||||||
case REFERENCE:
|
case REFERENCE:
|
||||||
return new SubscriptionMatchResult(theAndOrParams.stream().anyMatch(nextAnd -> matchParams(theResourceName, theParamName, theParamDef, nextAnd, theSearchParams)));
|
return new SubscriptionMatchResult(theAndOrParams.stream().anyMatch(nextAnd -> matchParams(theResourceName, theParamName, theParamDef, nextAnd, theSearchParams)), CRITERIA);
|
||||||
case COMPOSITE:
|
case COMPOSITE:
|
||||||
case HAS:
|
case HAS:
|
||||||
case SPECIAL:
|
case SPECIAL:
|
||||||
default:
|
default:
|
||||||
return new SubscriptionMatchResult(theParamName);
|
return new SubscriptionMatchResult(theParamName, CRITERIA);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (Constants.PARAM_CONTENT.equals(theParamName) || Constants.PARAM_TEXT.equals(theParamName)) {
|
if (Constants.PARAM_CONTENT.equals(theParamName) || Constants.PARAM_TEXT.equals(theParamName)) {
|
||||||
return new SubscriptionMatchResult(theParamName);
|
return new SubscriptionMatchResult(theParamName, CRITERIA);
|
||||||
} else {
|
} else {
|
||||||
throw new InvalidRequestException("Unknown search parameter " + theParamName + " for resource type " + theResourceName);
|
throw new InvalidRequestException("Unknown search parameter " + theParamName + " for resource type " + theResourceName);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.subscription.module.matcher;
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -21,34 +21,34 @@ package ca.uhn.fhir.jpa.subscription.module.matcher;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class SubscriptionMatchResult {
|
public class SubscriptionMatchResult {
|
||||||
// This could be an enum, but we may want to include details about unsupported matches in the future
|
|
||||||
public static final SubscriptionMatchResult MATCH = new SubscriptionMatchResult(true);
|
|
||||||
public static final SubscriptionMatchResult NO_MATCH = new SubscriptionMatchResult(false);
|
|
||||||
|
|
||||||
private final boolean myMatch;
|
private final boolean myMatch;
|
||||||
private final boolean mySupported;
|
private final boolean mySupported;
|
||||||
private final String myUnsupportedParameter;
|
private final String myUnsupportedParameter;
|
||||||
private final String myUnsupportedReason;
|
private final String myUnsupportedReason;
|
||||||
|
private final String myMatcherShortName;
|
||||||
|
|
||||||
public SubscriptionMatchResult(boolean theMatch) {
|
public SubscriptionMatchResult(boolean theMatch, String theMatcherShortName) {
|
||||||
this.myMatch = theMatch;
|
this.myMatch = theMatch;
|
||||||
this.mySupported = true;
|
this.mySupported = true;
|
||||||
this.myUnsupportedParameter = null;
|
this.myUnsupportedParameter = null;
|
||||||
this.myUnsupportedReason = null;
|
this.myUnsupportedReason = null;
|
||||||
|
this.myMatcherShortName = theMatcherShortName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SubscriptionMatchResult(String theUnsupportedParameter) {
|
public SubscriptionMatchResult(String theUnsupportedParameter, String theMatcherShortName) {
|
||||||
this.myMatch = false;
|
this.myMatch = false;
|
||||||
this.mySupported = false;
|
this.mySupported = false;
|
||||||
this.myUnsupportedParameter = theUnsupportedParameter;
|
this.myUnsupportedParameter = theUnsupportedParameter;
|
||||||
this.myUnsupportedReason = "Parameter not supported";
|
this.myUnsupportedReason = "Parameter not supported";
|
||||||
|
this.myMatcherShortName = theMatcherShortName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SubscriptionMatchResult(String theUnsupportedParameter, String theUnsupportedReason) {
|
public SubscriptionMatchResult(String theUnsupportedParameter, String theUnsupportedReason, String theMatcherShortName) {
|
||||||
this.myMatch = false;
|
this.myMatch = false;
|
||||||
this.mySupported = false;
|
this.mySupported = false;
|
||||||
this.myUnsupportedParameter = theUnsupportedParameter;
|
this.myUnsupportedParameter = theUnsupportedParameter;
|
||||||
this.myUnsupportedReason = theUnsupportedReason;
|
this.myUnsupportedReason = theUnsupportedReason;
|
||||||
|
this.myMatcherShortName = theMatcherShortName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean supported() {
|
public boolean supported() {
|
||||||
|
@ -62,4 +62,12 @@ public class SubscriptionMatchResult {
|
||||||
public String getUnsupportedReason() {
|
public String getUnsupportedReason() {
|
||||||
return "Parameter: <" + myUnsupportedParameter + "> Reason: " + myUnsupportedReason;
|
return "Parameter: <" + myUnsupportedParameter + "> Reason: " + myUnsupportedReason;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a short name of the matcher that generated this
|
||||||
|
* response, for use in logging
|
||||||
|
*/
|
||||||
|
public String matcherShortName() {
|
||||||
|
return myMatcherShortName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription;
|
import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;
|
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher;
|
import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher;
|
||||||
|
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchResult;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -113,11 +114,12 @@ public class SubscriptionMatchingSubscriber implements MessageHandler {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mySubscriptionMatcher.match(nextCriteriaString, theMsg).matched()) {
|
SubscriptionMatchResult matchResult = mySubscriptionMatcher.match(nextCriteriaString, theMsg);
|
||||||
|
if (!matchResult.matched()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ourLog.debug("Found match: queueing rest-hook notification for resource: {}", id.toUnqualifiedVersionless().getValue());
|
ourLog.info("Subscription {} was matched by resource {} using matcher {}", nextActiveSubscription.getSubscription().getIdElement(myFhirContext).getValue(), id.toUnqualifiedVersionless().getValue(), matchResult.matcherShortName());
|
||||||
|
|
||||||
ResourceDeliveryMessage deliveryMsg = new ResourceDeliveryMessage();
|
ResourceDeliveryMessage deliveryMsg = new ResourceDeliveryMessage();
|
||||||
deliveryMsg.setPayload(myFhirContext, theMsg.getNewPayload(myFhirContext));
|
deliveryMsg.setPayload(myFhirContext, theMsg.getNewPayload(myFhirContext));
|
||||||
|
@ -130,7 +132,7 @@ public class SubscriptionMatchingSubscriber implements MessageHandler {
|
||||||
if (deliveryChannel != null) {
|
if (deliveryChannel != null) {
|
||||||
deliveryChannel.send(wrappedMsg);
|
deliveryChannel.send(wrappedMsg);
|
||||||
} else {
|
} else {
|
||||||
ourLog.warn("Do not have deliovery channel for subscription {}", nextActiveSubscription.getIdElement(myFhirContext));
|
ourLog.warn("Do not have delivery channel for subscription {}", nextActiveSubscription.getIdElement(myFhirContext));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package ca.uhn.fhir.rest.api.server;
|
package ca.uhn.fhir.rest.api.server;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.api.BundleInclusionRule;
|
import ca.uhn.fhir.context.api.BundleInclusionRule;
|
||||||
|
import ca.uhn.fhir.rest.api.PreferReturnEnum;
|
||||||
import ca.uhn.fhir.rest.server.IPagingProvider;
|
import ca.uhn.fhir.rest.server.IPagingProvider;
|
||||||
import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
|
import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
|
||||||
|
|
||||||
|
@ -30,4 +31,7 @@ public interface IRestfulServer<T extends RequestDetails> extends IRestfulServer
|
||||||
|
|
||||||
BundleInclusionRule getBundleInclusionRule();
|
BundleInclusionRule getBundleInclusionRule();
|
||||||
|
|
||||||
|
void setDefaultPreferReturn(PreferReturnEnum theDefaultPreferReturn);
|
||||||
|
|
||||||
|
PreferReturnEnum getDefaultPreferReturn();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.server;
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -31,10 +31,7 @@ import ca.uhn.fhir.parser.IParser;
|
||||||
import ca.uhn.fhir.rest.annotation.Destroy;
|
import ca.uhn.fhir.rest.annotation.Destroy;
|
||||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Initialize;
|
import ca.uhn.fhir.rest.annotation.Initialize;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.*;
|
||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
|
||||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
|
||||||
import ca.uhn.fhir.rest.api.server.IFhirVersionServer;
|
import ca.uhn.fhir.rest.api.server.IFhirVersionServer;
|
||||||
import ca.uhn.fhir.rest.api.server.IRestfulServer;
|
import ca.uhn.fhir.rest.api.server.IRestfulServer;
|
||||||
import ca.uhn.fhir.rest.api.server.ParseAction;
|
import ca.uhn.fhir.rest.api.server.ParseAction;
|
||||||
|
@ -55,6 +52,8 @@ import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.UnavailableException;
|
import javax.servlet.UnavailableException;
|
||||||
|
@ -96,8 +95,12 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||||
*/
|
*/
|
||||||
public static final String SERVLET_CONTEXT_ATTRIBUTE = "ca.uhn.fhir.rest.server.RestfulServer.servlet_context";
|
public static final String SERVLET_CONTEXT_ATTRIBUTE = "ca.uhn.fhir.rest.server.RestfulServer.servlet_context";
|
||||||
private static final ExceptionHandlingInterceptor DEFAULT_EXCEPTION_HANDLER = new ExceptionHandlingInterceptor();
|
private static final ExceptionHandlingInterceptor DEFAULT_EXCEPTION_HANDLER = new ExceptionHandlingInterceptor();
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServer.class);
|
private static final Logger ourLog = LoggerFactory.getLogger(RestfulServer.class);
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
/**
|
||||||
|
* Default value for {@link #setDefaultPreferReturn(PreferReturnEnum)}
|
||||||
|
*/
|
||||||
|
public static final PreferReturnEnum DEFAULT_PREFER_RETURN = PreferReturnEnum.REPRESENTATION;
|
||||||
private final List<IServerInterceptor> myInterceptors = new ArrayList<>();
|
private final List<IServerInterceptor> myInterceptors = new ArrayList<>();
|
||||||
private final List<Object> myPlainProviders = new ArrayList<>();
|
private final List<Object> myPlainProviders = new ArrayList<>();
|
||||||
private final List<IResourceProvider> myResourceProviders = new ArrayList<>();
|
private final List<IResourceProvider> myResourceProviders = new ArrayList<>();
|
||||||
|
@ -126,6 +129,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||||
private boolean myUseBrowserFriendlyContentTypes;
|
private boolean myUseBrowserFriendlyContentTypes;
|
||||||
private ITenantIdentificationStrategy myTenantIdentificationStrategy;
|
private ITenantIdentificationStrategy myTenantIdentificationStrategy;
|
||||||
private Date myConformanceDate;
|
private Date myConformanceDate;
|
||||||
|
private PreferReturnEnum myDefaultPreferReturn = DEFAULT_PREFER_RETURN;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor. Note that if no {@link FhirContext} is passed in to the server (either through the constructor, or
|
* Constructor. Note that if no {@link FhirContext} is passed in to the server (either through the constructor, or
|
||||||
|
@ -823,7 +827,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||||
|
|
||||||
String completeUrl;
|
String completeUrl;
|
||||||
Map<String, String[]> params = null;
|
Map<String, String[]> params = null;
|
||||||
if (StringUtils.isNotBlank(theRequest.getQueryString())) {
|
if (isNotBlank(theRequest.getQueryString())) {
|
||||||
completeUrl = requestUrl + "?" + theRequest.getQueryString();
|
completeUrl = requestUrl + "?" + theRequest.getQueryString();
|
||||||
/*
|
/*
|
||||||
* By default, we manually parse the request params (the URL params, or the body for
|
* By default, we manually parse the request params (the URL params, or the body for
|
||||||
|
@ -1629,6 +1633,39 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||||
theResponse.getWriter().write(theException.getMessage());
|
theResponse.getWriter().write(theException.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default, server create/update/patch/transaction methods return a copy of the resource
|
||||||
|
* as it was stored. This may be overridden by the client using the
|
||||||
|
* <code>Prefer</code> header.
|
||||||
|
* <p>
|
||||||
|
* This setting changes the default behaviour if no Prefer header is supplied by the client.
|
||||||
|
* The default is {@link PreferReturnEnum#REPRESENTATION}
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @see <a href="http://hl7.org/fhir/http.html#ops">HL7 FHIR Specification</a> section on the Prefer header
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public PreferReturnEnum getDefaultPreferReturn() {
|
||||||
|
return myDefaultPreferReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default, server create/update/patch/transaction methods return a copy of the resource
|
||||||
|
* as it was stored. This may be overridden by the client using the
|
||||||
|
* <code>Prefer</code> header.
|
||||||
|
* <p>
|
||||||
|
* This setting changes the default behaviour if no Prefer header is supplied by the client.
|
||||||
|
* The default is {@link PreferReturnEnum#REPRESENTATION}
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @see <a href="http://hl7.org/fhir/http.html#ops">HL7 FHIR Specification</a> section on the Prefer header
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setDefaultPreferReturn(PreferReturnEnum theDefaultPreferReturn) {
|
||||||
|
Validate.notNull(theDefaultPreferReturn, "theDefaultPreferReturn must not be null");
|
||||||
|
myDefaultPreferReturn = theDefaultPreferReturn;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Count length of URL string, but treating unescaped sequences (e.g. ' ') as their unescaped equivalent (%20)
|
* Count length of URL string, but treating unescaped sequences (e.g. ' ') as their unescaped equivalent (%20)
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.server.method;
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -21,28 +21,9 @@ package ca.uhn.fhir.rest.server.method;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
|
||||||
|
|
||||||
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.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.*;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
|
||||||
import ca.uhn.fhir.rest.api.PreferReturnEnum;
|
|
||||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
|
||||||
import ca.uhn.fhir.rest.api.SummaryEnum;
|
|
||||||
import ca.uhn.fhir.rest.api.server.IRestfulResponse;
|
import ca.uhn.fhir.rest.api.server.IRestfulResponse;
|
||||||
import ca.uhn.fhir.rest.api.server.IRestfulServer;
|
import ca.uhn.fhir.rest.api.server.IRestfulServer;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
@ -52,6 +33,18 @@ import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<MethodOutcome> {
|
abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<MethodOutcome> {
|
||||||
static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseOutcomeReturningMethodBinding.class);
|
static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseOutcomeReturningMethodBinding.class);
|
||||||
|
@ -198,19 +191,24 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allowPrefer) {
|
if (allowPrefer) {
|
||||||
outcome = resource;
|
|
||||||
String prefer = theRequest.getHeader(Constants.HEADER_PREFER);
|
String prefer = theRequest.getHeader(Constants.HEADER_PREFER);
|
||||||
PreferReturnEnum preferReturn = RestfulServerUtils.parsePreferHeader(prefer);
|
PreferReturnEnum preferReturn = RestfulServerUtils.parsePreferHeader(prefer);
|
||||||
if (preferReturn != null) {
|
if (preferReturn == null) {
|
||||||
if (preferReturn == PreferReturnEnum.MINIMAL) {
|
preferReturn = theServer.getDefaultPreferReturn();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (preferReturn) {
|
||||||
|
case REPRESENTATION:
|
||||||
|
outcome = resource;
|
||||||
|
break;
|
||||||
|
case MINIMAL:
|
||||||
outcome = null;
|
outcome = null;
|
||||||
}
|
break;
|
||||||
else {
|
case OPERATION_OUTCOME:
|
||||||
if (preferReturn == PreferReturnEnum.OPERATION_OUTCOME) {
|
outcome = originalOutcome;
|
||||||
outcome = originalOutcome;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ResponseDetails responseDetails = new ResponseDetails();
|
ResponseDetails responseDetails = new ResponseDetails();
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
package ca.uhn.fhir.rest.server;
|
package ca.uhn.fhir.rest.server;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import static org.hamcrest.Matchers.not;
|
import ca.uhn.fhir.rest.annotation.*;
|
||||||
import static org.hamcrest.Matchers.stringContainsInOrder;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import static org.junit.Assert.assertEquals;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
import static org.junit.Assert.assertNull;
|
import ca.uhn.fhir.rest.api.PreferReturnEnum;
|
||||||
import static org.junit.Assert.assertThat;
|
import ca.uhn.fhir.rest.client.MyPatientWithExtensions;
|
||||||
|
import ca.uhn.fhir.util.PortUtil;
|
||||||
import java.nio.charset.StandardCharsets;
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
import org.apache.http.client.methods.HttpGet;
|
import org.apache.http.client.methods.HttpGet;
|
||||||
import org.apache.http.client.methods.HttpPost;
|
import org.apache.http.client.methods.HttpPost;
|
||||||
import org.apache.http.entity.ContentType;
|
import org.apache.http.entity.ContentType;
|
||||||
|
@ -24,37 +21,35 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.servlet.ServletHandler;
|
import org.eclipse.jetty.servlet.ServletHandler;
|
||||||
import org.eclipse.jetty.servlet.ServletHolder;
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.r4.model.DateType;
|
import org.hl7.fhir.r4.model.DateType;
|
||||||
import org.hl7.fhir.r4.model.IdType;
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
import org.hl7.fhir.r4.model.OperationOutcome;
|
import org.hl7.fhir.r4.model.OperationOutcome;
|
||||||
import org.hl7.fhir.r4.model.OperationOutcome.OperationOutcomeIssueComponent;
|
import org.hl7.fhir.r4.model.OperationOutcome.OperationOutcomeIssueComponent;
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
import org.junit.AfterClass;
|
import org.junit.*;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import java.nio.charset.StandardCharsets;
|
||||||
import ca.uhn.fhir.rest.annotation.Create;
|
import java.util.ArrayList;
|
||||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
import java.util.List;
|
||||||
import ca.uhn.fhir.rest.annotation.Read;
|
import java.util.concurrent.TimeUnit;
|
||||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
|
||||||
import ca.uhn.fhir.rest.annotation.Search;
|
import static org.hamcrest.Matchers.*;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import static org.junit.Assert.*;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
|
||||||
import ca.uhn.fhir.rest.client.MyPatientWithExtensions;
|
|
||||||
import ca.uhn.fhir.util.PortUtil;
|
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
|
||||||
|
|
||||||
public class CreateR4Test {
|
public class CreateR4Test {
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CreateR4Test.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CreateR4Test.class);
|
||||||
public static IBaseOperationOutcome ourReturnOo;
|
public static OperationOutcome ourReturnOo;
|
||||||
private static CloseableHttpClient ourClient;
|
private static CloseableHttpClient ourClient;
|
||||||
private static FhirContext ourCtx = FhirContext.forR4();
|
private static FhirContext ourCtx = FhirContext.forR4();
|
||||||
private static int ourPort;
|
private static int ourPort;
|
||||||
private static Server ourServer;
|
private static Server ourServer;
|
||||||
|
private static RestfulServer ourServlet;
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void after() {
|
||||||
|
ourServlet.setDefaultPreferReturn(RestfulServer.DEFAULT_PREFER_RETURN);
|
||||||
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void before() {
|
public void before() {
|
||||||
|
@ -122,12 +117,12 @@ public class CreateR4Test {
|
||||||
|
|
||||||
assertThat(responseContent, containsString("DIAG"));
|
assertThat(responseContent, containsString("DIAG"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateReturnsRepresentation() throws Exception {
|
public void testCreateReturnsRepresentation() throws Exception {
|
||||||
ourReturnOo = new OperationOutcome().addIssue(new OperationOutcomeIssueComponent().setDiagnostics("DIAG"));
|
ourReturnOo = new OperationOutcome().addIssue(new OperationOutcomeIssueComponent().setDiagnostics("DIAG"));
|
||||||
String expectedResponseContent = "{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\"},\"gender\":\"male\"}";
|
String expectedResponseContent = "{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\"},\"gender\":\"male\"}";
|
||||||
|
|
||||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
|
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
|
||||||
httpPost.setEntity(new StringEntity("{\"resourceType\":\"Patient\", \"gender\":\"male\"}", ContentType.parse("application/fhir+json; charset=utf-8")));
|
httpPost.setEntity(new StringEntity("{\"resourceType\":\"Patient\", \"gender\":\"male\"}", ContentType.parse("application/fhir+json; charset=utf-8")));
|
||||||
HttpResponse status = ourClient.execute(httpPost);
|
HttpResponse status = ourClient.execute(httpPost);
|
||||||
|
@ -219,6 +214,79 @@ public class CreateR4Test {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreatePreferDefaultRepresentation() throws Exception {
|
||||||
|
ourReturnOo = new OperationOutcome();
|
||||||
|
ourReturnOo.addIssue().setDiagnostics("FOO");
|
||||||
|
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.setActive(true);
|
||||||
|
String body = ourCtx.newJsonParser().encodeResourceToString(p);
|
||||||
|
HttpPost httpPost;
|
||||||
|
|
||||||
|
httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
|
||||||
|
httpPost.setEntity(new StringEntity(body, ContentType.parse("application/fhir+json; charset=utf-8")));
|
||||||
|
try (CloseableHttpResponse status = ourClient.execute(httpPost)) {
|
||||||
|
|
||||||
|
assertEquals(201, status.getStatusLine().getStatusCode());
|
||||||
|
assertEquals("application/fhir+json;charset=utf-8", status.getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
|
||||||
|
|
||||||
|
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
ourLog.info("Response was:\n{}", responseContent);
|
||||||
|
assertThat(responseContent, containsString("\"resourceType\":\"Patient\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreatePreferDefaultOperationOutcome() throws Exception {
|
||||||
|
ourReturnOo = new OperationOutcome();
|
||||||
|
ourReturnOo.addIssue().setDiagnostics("FOO");
|
||||||
|
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.setActive(true);
|
||||||
|
String body = ourCtx.newJsonParser().encodeResourceToString(p);
|
||||||
|
|
||||||
|
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
|
||||||
|
httpPost.setEntity(new StringEntity(body, ContentType.parse("application/fhir+json; charset=utf-8")));
|
||||||
|
ourServlet.setDefaultPreferReturn(PreferReturnEnum.OPERATION_OUTCOME);
|
||||||
|
try (CloseableHttpResponse status = ourClient.execute(httpPost)) {
|
||||||
|
|
||||||
|
assertEquals(201, status.getStatusLine().getStatusCode());
|
||||||
|
assertEquals("application/fhir+json;charset=utf-8", status.getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
|
||||||
|
|
||||||
|
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
ourLog.info("Response was:\n{}", responseContent);
|
||||||
|
assertThat(responseContent, containsString("\"resourceType\":\"OperationOutcome\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreatePreferDefaultMinimal() throws Exception {
|
||||||
|
ourReturnOo = new OperationOutcome();
|
||||||
|
ourReturnOo.addIssue().setDiagnostics("FOO");
|
||||||
|
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.setActive(true);
|
||||||
|
String body = ourCtx.newJsonParser().encodeResourceToString(p);
|
||||||
|
|
||||||
|
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
|
||||||
|
httpPost.setEntity(new StringEntity(body, ContentType.parse("application/fhir+json; charset=utf-8")));
|
||||||
|
ourServlet.setDefaultPreferReturn(PreferReturnEnum.MINIMAL);
|
||||||
|
try (CloseableHttpResponse status = ourClient.execute(httpPost)) {
|
||||||
|
|
||||||
|
assertEquals(201, status.getStatusLine().getStatusCode());
|
||||||
|
assertNull(status.getFirstHeader(Constants.HEADER_CONTENT_TYPE));
|
||||||
|
|
||||||
|
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
assertThat(responseContent, emptyOrNullString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearch() throws Exception {
|
public void testSearch() throws Exception {
|
||||||
|
|
||||||
|
@ -248,36 +316,6 @@ public class CreateR4Test {
|
||||||
assertThat(responseContent, not(containsString("http://hl7.org/fhir/")));
|
assertThat(responseContent, not(containsString("http://hl7.org/fhir/")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
|
||||||
public static void afterClassClearContext() throws Exception {
|
|
||||||
ourServer.stop();
|
|
||||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void beforeClass() throws Exception {
|
|
||||||
ourPort = PortUtil.findFreePort();
|
|
||||||
ourServer = new Server(ourPort);
|
|
||||||
|
|
||||||
PatientProviderCreate patientProviderCreate = new PatientProviderCreate();
|
|
||||||
PatientProviderRead patientProviderRead = new PatientProviderRead();
|
|
||||||
PatientProviderSearch patientProviderSearch = new PatientProviderSearch();
|
|
||||||
|
|
||||||
ServletHandler proxyHandler = new ServletHandler();
|
|
||||||
RestfulServer servlet = new RestfulServer(ourCtx);
|
|
||||||
|
|
||||||
servlet.setResourceProviders(patientProviderCreate, patientProviderRead, patientProviderSearch);
|
|
||||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
|
||||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
|
||||||
ourServer.setHandler(proxyHandler);
|
|
||||||
ourServer.start();
|
|
||||||
|
|
||||||
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
|
||||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
|
||||||
builder.setConnectionManager(connectionManager);
|
|
||||||
ourClient = builder.build();
|
|
||||||
|
|
||||||
}
|
|
||||||
public static class PatientProviderRead implements IResourceProvider {
|
public static class PatientProviderRead implements IResourceProvider {
|
||||||
|
|
||||||
@Read()
|
@Read()
|
||||||
|
@ -299,12 +337,13 @@ public class CreateR4Test {
|
||||||
public Class<Patient> getResourceType() {
|
public Class<Patient> getResourceType() {
|
||||||
return Patient.class;
|
return Patient.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Create()
|
@Create()
|
||||||
public MethodOutcome create(@ResourceParam Patient theIdParam) {
|
public MethodOutcome create(@ResourceParam Patient thePatient) {
|
||||||
assertNull(theIdParam.getIdElement().getIdPart());
|
assertNull(thePatient.getIdElement().getIdPart());
|
||||||
theIdParam.setId("1");
|
thePatient.setId("1");
|
||||||
theIdParam.getMeta().setVersionId("1");
|
thePatient.getMeta().setVersionId("1");
|
||||||
return new MethodOutcome(new IdType("Patient", "1"), true).setOperationOutcome(ourReturnOo).setResource(theIdParam);
|
return new MethodOutcome(new IdType("Patient", "1"), true).setOperationOutcome(ourReturnOo).setResource(thePatient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,4 +375,35 @@ public class CreateR4Test {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void afterClassClearContext() throws Exception {
|
||||||
|
ourServer.stop();
|
||||||
|
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void beforeClass() throws Exception {
|
||||||
|
ourPort = PortUtil.findFreePort();
|
||||||
|
ourServer = new Server(ourPort);
|
||||||
|
|
||||||
|
PatientProviderCreate patientProviderCreate = new PatientProviderCreate();
|
||||||
|
PatientProviderRead patientProviderRead = new PatientProviderRead();
|
||||||
|
PatientProviderSearch patientProviderSearch = new PatientProviderSearch();
|
||||||
|
|
||||||
|
ServletHandler proxyHandler = new ServletHandler();
|
||||||
|
ourServlet = new RestfulServer(ourCtx);
|
||||||
|
|
||||||
|
ourServlet.setResourceProviders(patientProviderCreate, patientProviderRead, patientProviderSearch);
|
||||||
|
ServletHolder servletHolder = new ServletHolder(ourServlet);
|
||||||
|
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||||
|
ourServer.setHandler(proxyHandler);
|
||||||
|
ourServer.start();
|
||||||
|
|
||||||
|
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
||||||
|
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||||
|
builder.setConnectionManager(connectionManager);
|
||||||
|
ourClient = builder.build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,21 +34,21 @@
|
||||||
subscription matching will query the database as before.
|
subscription matching will query the database as before.
|
||||||
</action>
|
</action>
|
||||||
<action type="change">
|
<action type="change">
|
||||||
Removed BaseSubscriptionInterceptor and all its subclasses (RestHook, EMail, WebSocket). These are replaced
|
Removed BaseSubscriptionInterceptor and all its subclasses (RestHook, EMail, WebSocket). These are replaced
|
||||||
by two new interceptors: SubscriptionActivatingInterceptor that is responsible for activating subscriptions
|
by two new interceptors: SubscriptionActivatingInterceptor that is responsible for activating subscriptions
|
||||||
and SubscriptionMatchingInterceptor that is responsible for matching incoming resources against activated
|
and SubscriptionMatchingInterceptor that is responsible for matching incoming resources against activated
|
||||||
subscriptions. Call DaoConfig.addSupportedSubscriptionType(type) to configure which subscription types
|
subscriptions. Call DaoConfig.addSupportedSubscriptionType(type) to configure which subscription types
|
||||||
are supported in your environment. The helper method SubscriptionInterceptorLoader.registerInterceptors()
|
are supported in your environment. The helper method SubscriptionInterceptorLoader.registerInterceptors()
|
||||||
will check if any subscription types are supported, and if so then load active subscriptions into the
|
will check if any subscription types are supported, and if so then load active subscriptions into the
|
||||||
SubscriptionRegistry and then register both the activating and matching interceptors.
|
SubscriptionRegistry and then register both the activating and matching interceptors.
|
||||||
See https://github.com/jamesagnew/hapi-fhir/wiki/Proposed-Subscription-Design-Change for more
|
See https://github.com/jamesagnew/hapi-fhir/wiki/Proposed-Subscription-Design-Change for more
|
||||||
details.
|
details.
|
||||||
</action>
|
</action>
|
||||||
<action type="change">
|
<action type="change">
|
||||||
Added support for matching subscriptions in a separate server from the REST Server. To do this, run the
|
Added support for matching subscriptions in a separate server from the REST Server. To do this, run the
|
||||||
SubscriptionActivatingInterceptor on the REST server and the SubscriptionMatchingInterceptor in the
|
SubscriptionActivatingInterceptor on the REST server and the SubscriptionMatchingInterceptor in the
|
||||||
standalone server. Classes required to support running a standalone subscription server are in the
|
standalone server. Classes required to support running a standalone subscription server are in the
|
||||||
ca.uhn.fhir.jpa.subscription.module.standalone package. These classes are excluded by default from
|
ca.uhn.fhir.jpa.subscription.module.standalone package. These classes are excluded by default from
|
||||||
the JPA ApplicationContext (that package is explicitly filtered out in the BaseConfig.java @ComponentScan).
|
the JPA ApplicationContext (that package is explicitly filtered out in the BaseConfig.java @ComponentScan).
|
||||||
</action>
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
|
@ -125,7 +125,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
JPA Migrator tool enhancements:
|
JPA Migrator tool enhancements:
|
||||||
An invalid SQL syntax issue has been fixed when running the CLI JPA Migrator tool against
|
An invalid SQL syntax issue has been fixed when running the CLI JPA Migrator tool against
|
||||||
Oracle or SQL Server. In addition, when using the "Dry Run" option, all generated SQL
|
Oracle or SQL Server. In addition, when using the "Dry Run" option, all generated SQL
|
||||||
statements will be logged at the end of the run. Also, a case sensitivity issue when running against
|
statements will be logged at the end of the run. Also, a case sensitivity issue when running against
|
||||||
some Postgres databases has been corrected.
|
some Postgres databases has been corrected.
|
||||||
|
@ -146,7 +146,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
The resource reindexer can now detect when a resource's current version no longer
|
The resource reindexer can now detect when a resource's current version no longer
|
||||||
exists in the database (e.g. because it was manually expunged), and can automatically
|
exists in the database (e.g. because it was manually expunged), and can automatically
|
||||||
adjust the most recent version to
|
adjust the most recent version to
|
||||||
account for this.
|
account for this.
|
||||||
</action>
|
</action>
|
||||||
|
@ -171,20 +171,21 @@
|
||||||
the correct content type cia the Accept header.
|
the correct content type cia the Accept header.
|
||||||
</action>
|
</action>
|
||||||
<action type="add" issue="917">
|
<action type="add" issue="917">
|
||||||
A new configuration item has been added to the FhirInstanceValidator that
|
A new configuration item has been added to the FhirInstanceValidator that
|
||||||
allows you to specify additional "known extension domains", meaning
|
allows you to specify additional "known extension domains", meaning
|
||||||
domains in which the validator will not complain about when it
|
domains in which the validator will not complain about when it
|
||||||
encounters new extensions. Thanks to Heinz-Dieter Conradi for the
|
encounters new extensions. Thanks to Heinz-Dieter Conradi for the
|
||||||
pull request!
|
pull request!
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
Under some circumstances, when a custom search parameter was added to the JPA server
|
Under some circumstances, when a custom search parameter was added to the JPA server
|
||||||
resources could start reindexing before the new search parameter had been saved, meaning that
|
resources could start reindexing before the new search parameter had been saved, meaning that
|
||||||
it was not applied to all resources. This has been corrected.
|
it was not applied to all resources. This has been corrected.
|
||||||
</action>
|
</action>
|
||||||
<action type="change">
|
<action type="change">
|
||||||
In example-projects/README.md and hapi-fhir-jpaserver-example/README.md, incidate that these examples projects
|
In example-projects/README.md and hapi-fhir-jpaserver-example/README.md, incidate that these examples
|
||||||
are no longer maintained. The README.md points users to a starter project they should use for examples.
|
projects
|
||||||
|
are no longer maintained. The README.md points users to a starter project they should use for examples.
|
||||||
</action>
|
</action>
|
||||||
<action type="change">
|
<action type="change">
|
||||||
Replaced use of BeanFactory with custom factory classes that Spring @Lookup the @Scope("prototype") beans
|
Replaced use of BeanFactory with custom factory classes that Spring @Lookup the @Scope("prototype") beans
|
||||||
|
@ -194,27 +195,27 @@
|
||||||
Moved e-mail from address configuration from EmailInterceptor (which doesn't exist any more) to DaoConfig.
|
Moved e-mail from address configuration from EmailInterceptor (which doesn't exist any more) to DaoConfig.
|
||||||
</action>
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
Added 3 interfaces for services required by the standalone subscription server. The standalone subscription
|
Added 3 interfaces for services required by the standalone subscription server. The standalone subscription
|
||||||
server doesn't have access to a database and so needs to get its resources using a FhirClient. Thus
|
server doesn't have access to a database and so needs to get its resources using a FhirClient. Thus
|
||||||
for each of these interfaces, there are two implementations: a Dao implementaiton and a FhirClient
|
for each of these interfaces, there are two implementations: a Dao implementaiton and a FhirClient
|
||||||
implementation. The interfaces thus introduced are ISubscriptionProvider (used to load subscriptions
|
implementation. The interfaces thus introduced are ISubscriptionProvider (used to load subscriptions
|
||||||
into the SubscriptionRegistry), the IResourceProvider (used to get the latest version of a resource
|
into the SubscriptionRegistry), the IResourceProvider (used to get the latest version of a resource
|
||||||
if the "get latest version" flag is set on the subscription) and ISearchParamProvider used to load
|
if the "get latest version" flag is set on the subscription) and ISearchParamProvider used to load
|
||||||
custom search parameters.
|
custom search parameters.
|
||||||
</action>
|
</action>
|
||||||
<action type="change">
|
<action type="change">
|
||||||
Separated active subscription cache from the interceptors into a new Spring component called the
|
Separated active subscription cache from the interceptors into a new Spring component called the
|
||||||
SubscriptionRegistry. This component maintains a cache of ActiveSubscriptions. An ActiveSubscription
|
SubscriptionRegistry. This component maintains a cache of ActiveSubscriptions. An ActiveSubscription
|
||||||
contains the subscription, it's delivery channel, and a list of delivery handlers.
|
contains the subscription, it's delivery channel, and a list of delivery handlers.
|
||||||
</action>
|
</action>
|
||||||
<action type="change">
|
<action type="change">
|
||||||
Introduced a new Spring factory interface ISubscribableChannelFactory that is used to create delivery
|
Introduced a new Spring factory interface ISubscribableChannelFactory that is used to create delivery
|
||||||
channels and handlers. By default, HAPI FHIR ships with a LinkedBlockingQueue implementation of the
|
channels and handlers. By default, HAPI FHIR ships with a LinkedBlockingQueue implementation of the
|
||||||
delivery channel factory. If a different type of channel factory is required (e.g. JMS or Kafka), add it
|
delivery channel factory. If a different type of channel factory is required (e.g. JMS or Kafka), add it
|
||||||
to your application context and mark it as @Primary.
|
to your application context and mark it as @Primary.
|
||||||
</action>
|
</action>
|
||||||
<action type="fix" issue="980">
|
<action type="fix" issue="980">
|
||||||
When using the HL7.org DSTU2 structures, a QuestionnaireResponse with a
|
When using the HL7.org DSTU2 structures, a QuestionnaireResponse with a
|
||||||
value of type reference would fail to parse. Thanks to David Gileadi for
|
value of type reference would fail to parse. Thanks to David Gileadi for
|
||||||
the pull request!
|
the pull request!
|
||||||
</action>
|
</action>
|
||||||
|
@ -231,6 +232,17 @@
|
||||||
JPA Subscription deliveries did not always include the accurate versionId if the Subscription
|
JPA Subscription deliveries did not always include the accurate versionId if the Subscription
|
||||||
module was configured to use an external queuing engine. This has been corrected.
|
module was configured to use an external queuing engine. This has been corrected.
|
||||||
</action>
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
It is now possible in a plain or JPA server to specify the default return
|
||||||
|
type for create/update operations when no Prefer header has been provided
|
||||||
|
by the client.
|
||||||
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
It is now possible in a JPA server to specify the _total calculation
|
||||||
|
behaviour if no parameter is supplied by the client. This is done using a
|
||||||
|
new setting on the DaoConfig. This can be used to force a total to
|
||||||
|
always be calculated for searches, including large ones.
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="3.6.0" date="2018-11-12" description="Food">
|
<release version="3.6.0" date="2018-11-12" description="Food">
|
||||||
<action type="add">
|
<action type="add">
|
||||||
|
|
Loading…
Reference in New Issue