Add config options for default Prefer header and _total param on server

This commit is contained in:
James Agnew 2019-01-04 16:12:45 -05:00
parent 8c87c7c089
commit 5b8fee869e
16 changed files with 402 additions and 189 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -183,7 +183,8 @@
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
projects
are no longer maintained. The README.md points users to a starter project they should use for examples. 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">
@ -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">