Allow client assigned IDs to be purely numeric in JPA server if

configured to do so
This commit is contained in:
James Agnew 2018-11-07 18:25:50 -05:00 committed by Eeva Turkka
parent 60e81cfb67
commit 821da83d43
27 changed files with 548 additions and 228 deletions

View File

@ -82,6 +82,7 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.failedToCreateWithInvalidId=Can not
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.incorrectResourceType=Incorrect resource type detected for endpoint, found {0} but expected {1}
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.failedToCreateWithClientAssignedNumericId=Can not create resource with ID[{0}], no resource with this ID exists and clients may only assign IDs which contain at least one non-numeric character
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.failedToCreateWithClientAssignedId=Can not create resource with ID[{0}], ID must not be supplied on a create (POST) operation
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.failedToCreateWithClientAssignedIdNotAllowed=No resource exists on this server resource with ID[{0}], and client-assigned IDs are not enabled.
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidParameterChain=Invalid parameter chain: {0}
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidVersion=Version "{0}" is not valid for resource {1}
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.multipleParamsWithSameNameOneIsMissingTrue=This server does not know how to handle multiple "{0}" parameters where one has a value of :missing=true

View File

@ -34,10 +34,7 @@ import ca.uhn.fhir.jpa.sp.SearchParamPresenceSvcImpl;
import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor;
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.hibernate.query.criteria.LiteralHandlingMode;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@ -57,7 +54,6 @@ import org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import javax.annotation.Nonnull;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
@ -92,44 +88,7 @@ public abstract class BaseConfig implements SchedulingConfigurer {
* factory with HAPI FHIR customizations
*/
protected LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = new LocalContainerEntityManagerFactoryBean() {
@Override
public Map<String, Object> getJpaPropertyMap() {
Map<String, Object> retVal = super.getJpaPropertyMap();
if (!retVal.containsKey(AvailableSettings.CRITERIA_LITERAL_HANDLING_MODE)) {
retVal.put(AvailableSettings.CRITERIA_LITERAL_HANDLING_MODE, LiteralHandlingMode.BIND);
}
if (!retVal.containsKey(AvailableSettings.CONNECTION_HANDLING)) {
retVal.put(AvailableSettings.CONNECTION_HANDLING, PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_HOLD);
}
/*
* Set some performance options
*/
if (!retVal.containsKey(AvailableSettings.STATEMENT_BATCH_SIZE)) {
retVal.put(AvailableSettings.STATEMENT_BATCH_SIZE, "30");
}
if (!retVal.containsKey(AvailableSettings.ORDER_INSERTS)) {
retVal.put(AvailableSettings.ORDER_INSERTS, "true");
}
if (!retVal.containsKey(AvailableSettings.ORDER_UPDATES)) {
retVal.put(AvailableSettings.ORDER_UPDATES, "true");
}
if (!retVal.containsKey(AvailableSettings.BATCH_VERSIONED_DATA)) {
retVal.put(AvailableSettings.BATCH_VERSIONED_DATA, "true");
}
return retVal;
}
};
LocalContainerEntityManagerFactoryBean retVal = new HapiFhirLocalContainerEntityManagerFactoryBean();
configureEntityManagerFactory(retVal, fhirContext());
return retVal;
}

View File

@ -0,0 +1,51 @@
package ca.uhn.fhir.jpa.config;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.query.criteria.LiteralHandlingMode;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import java.util.Map;
/**
* This class is an extension of the Spring/Hibernate LocalContainerEntityManagerFactoryBean
* that sets some sensible default property values
*/
public class HapiFhirLocalContainerEntityManagerFactoryBean extends LocalContainerEntityManagerFactoryBean {
@Override
public Map<String, Object> getJpaPropertyMap() {
Map<String, Object> retVal = super.getJpaPropertyMap();
if (!retVal.containsKey(AvailableSettings.CRITERIA_LITERAL_HANDLING_MODE)) {
retVal.put(AvailableSettings.CRITERIA_LITERAL_HANDLING_MODE, LiteralHandlingMode.BIND);
}
if (!retVal.containsKey(AvailableSettings.CONNECTION_HANDLING)) {
retVal.put(AvailableSettings.CONNECTION_HANDLING, PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_HOLD);
}
/*
* Set some performance options
*/
if (!retVal.containsKey(AvailableSettings.STATEMENT_BATCH_SIZE)) {
retVal.put(AvailableSettings.STATEMENT_BATCH_SIZE, "30");
}
if (!retVal.containsKey(AvailableSettings.ORDER_INSERTS)) {
retVal.put(AvailableSettings.ORDER_INSERTS, "true");
}
if (!retVal.containsKey(AvailableSettings.ORDER_UPDATES)) {
retVal.put(AvailableSettings.ORDER_UPDATES, "true");
}
if (!retVal.containsKey(AvailableSettings.BATCH_VERSIONED_DATA)) {
retVal.put(AvailableSettings.BATCH_VERSIONED_DATA, "true");
}
return retVal;
}
}

View File

@ -21,7 +21,6 @@ import java.util.Set;
import java.util.StringTokenizer;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
@ -37,6 +36,7 @@ import javax.persistence.criteria.Root;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.XMLEvent;
import ca.uhn.fhir.jpa.dao.data.*;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.Validate;
import org.apache.http.NameValuePair;
@ -81,21 +81,6 @@ import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeChildResourceDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTagDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamCoordsDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamDateDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamNumberDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamQuantityDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamStringDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamTokenDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
import ca.uhn.fhir.jpa.dao.data.IResourceSearchViewDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTagDao;
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.index.IndexingSupport;
import ca.uhn.fhir.jpa.dao.index.ResourceIndexedSearchParams;
import ca.uhn.fhir.jpa.entity.BaseHasResource;
@ -176,7 +161,6 @@ import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
@ -261,6 +245,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
protected EntityManager myEntityManager;
@Autowired
protected IForcedIdDao myForcedIdDao;
@Autowired
protected ISearchResultDao mySearchResultDao;
@Autowired(required = false)
protected IFulltextSearchSvc myFulltextSearchSvc;
@Autowired()
@ -319,10 +305,14 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
}
}
protected void createForcedIdIfNeeded(ResourceTable theEntity, IIdType theId) {
if (theId.isEmpty() == false && theId.hasIdPart()) {
if (isValidPid(theId)) {
return;
/**
* Returns the newly created forced ID. If the entity already had a forced ID, or if
* none was created, returns null.
*/
protected ForcedId createForcedIdIfNeeded(ResourceTable theEntity, IIdType theId, boolean theCreateForPureNumericIds) {
if (theId.isEmpty() == false && theId.hasIdPart() && theEntity.getForcedId() == null) {
if (!theCreateForPureNumericIds && isValidPid(theId)) {
return null;
}
ForcedId fid = new ForcedId();
@ -330,7 +320,10 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
fid.setForcedId(theId.getIdPart());
fid.setResource(theEntity);
theEntity.setForcedId(fid);
return fid;
}
return null;
}
protected ExpungeOutcome doExpunge(String theResourceName, Long theResourceId, Long theVersion, ExpungeOptions theExpungeOptions) {
@ -373,6 +366,16 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
}
});
/*
* Delete any search result cache entries pointing to the given resource
*/
if (resourceIds.getContent().size() > 0) {
txTemplate.execute(t -> {
mySearchResultDao.deleteByResourceIds(resourceIds.getContent());
return null;
});
}
/*
* Delete historical versions
*/
@ -416,6 +419,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
}
}
});
for (Long next : historicalIds) {
txTemplate.execute(t -> {
expungeHistoricalVersion(next);
@ -675,6 +679,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
return retVal;
}
@Override
public DaoConfig getConfig() {
return myConfig;
}
@ -705,6 +710,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
}
}
@Override
@SuppressWarnings("unchecked")
public <R extends IBaseResource> IFhirResourceDao<R> getDao(Class<R> theType) {
Map<Class<? extends IBaseResource>, IFhirResourceDao<?>> resourceTypeToDao = getDaos();
@ -886,6 +892,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
theProvider.setSearchCoordinatorSvc(mySearchCoordinatorSvc);
}
@Override
public boolean isLogicalReference(IIdType theId) {
Set<String> treatReferencesAsLogical = myConfig.getTreatReferencesAsLogical();
if (treatReferencesAsLogical != null) {
@ -1461,11 +1468,11 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
@Override
public Long translateForcedIdToPid(String theResourceName, String theResourceId) {
return translateForcedIdToPids(new IdDt(theResourceName, theResourceId), myForcedIdDao).get(0);
return translateForcedIdToPids(getConfig(), new IdDt(theResourceName, theResourceId), myForcedIdDao).get(0);
}
protected List<Long> translateForcedIdToPids(IIdType theId) {
return translateForcedIdToPids(theId, myForcedIdDao);
return translateForcedIdToPids(getConfig(), theId, myForcedIdDao);
}
@ -1649,7 +1656,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
boolean theForceUpdateVersion, RequestDetails theRequestDetails, ResourceTable theEntity, IIdType
theResourceId, IBaseResource theOldResource) {
// Notify interceptors
ActionRequestDetails requestDetails = null;
ActionRequestDetails requestDetails;
if (theRequestDetails != null) {
requestDetails = new ActionRequestDetails(theRequestDetails, theResource, theResourceId.getResourceType(), theResourceId);
notifyInterceptors(RestOperationTypeEnum.UPDATE, requestDetails);
@ -1988,15 +1995,15 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
return retVal;
}
protected static Long translateForcedIdToPid(String theResourceName, String theResourceId, IForcedIdDao
protected static Long translateForcedIdToPid(DaoConfig theDaoConfig, String theResourceName, String theResourceId, IForcedIdDao
theForcedIdDao) {
return translateForcedIdToPids(new IdDt(theResourceName, theResourceId), theForcedIdDao).get(0);
return translateForcedIdToPids(theDaoConfig, new IdDt(theResourceName, theResourceId), theForcedIdDao).get(0);
}
static List<Long> translateForcedIdToPids(IIdType theId, IForcedIdDao theForcedIdDao) {
static List<Long> translateForcedIdToPids(DaoConfig theDaoConfig, IIdType theId, IForcedIdDao theForcedIdDao) {
Validate.isTrue(theId.hasIdPart());
if (isValidPid(theId)) {
if (theDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY && isValidPid(theId)) {
return Collections.singletonList(theId.getIdPartAsLong());
} else {
List<ForcedId> forcedId;

View File

@ -53,16 +53,12 @@ import org.hl7.fhir.instance.model.api.*;
import org.hl7.fhir.r4.model.InstantType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.lang.NonNull;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import javax.persistence.NoResultException;
import javax.persistence.TypedQuery;
@ -387,12 +383,26 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
}
boolean serverAssignedId;
if (isNotBlank(theResource.getIdElement().getIdPart())) {
if (isValidPid(theResource.getIdElement())) {
throw new UnprocessableEntityException(
"This server cannot create an entity with a user-specified numeric ID - Client should not specify an ID when creating a new resource, or should include at least one letter in the ID to force a client-defined ID");
switch (myDaoConfig.getResourceClientIdStrategy()) {
case NOT_ALLOWED:
throw new ResourceNotFoundException(
getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedIdNotAllowed", theResource.getIdElement().getIdPart()));
case ALPHANUMERIC:
if (theResource.getIdElement().isIdPartValidLong()) {
throw new InvalidRequestException(
getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedNumericId", theResource.getIdElement().getIdPart()));
}
createForcedIdIfNeeded(entity, theResource.getIdElement());
createForcedIdIfNeeded(entity, theResource.getIdElement(), false);
break;
case ANY:
createForcedIdIfNeeded(entity, theResource.getIdElement(), true);
break;
}
serverAssignedId = false;
} else {
serverAssignedId = true;
}
// Notify interceptors
@ -413,8 +423,21 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
// Perform actual DB update
ResourceTable updatedEntity = updateEntity(theRequest, theResource, entity, null, thePerformIndexing, thePerformIndexing, theUpdateTime, false, thePerformIndexing);
theResource.setId(entity.getIdDt());
theResource.setId(entity.getIdDt());
if (serverAssignedId) {
switch (myDaoConfig.getResourceClientIdStrategy()) {
case NOT_ALLOWED:
case ALPHANUMERIC:
break;
case ANY:
ForcedId forcedId = createForcedIdIfNeeded(updatedEntity, theResource.getIdElement(), true);
if (forcedId != null) {
myForcedIdDao.save(forcedId);
}
break;
}
}
/*
* If we aren't indexing (meaning we're probably executing a sub-operation within a transaction),
@ -1231,10 +1254,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
try {
entity = readEntityLatestVersion(resourceId);
} catch (ResourceNotFoundException e) {
if (resourceId.isIdPartValidLong()) {
throw new InvalidRequestException(
getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedNumericId", theResource.getIdElement().getIdPart()));
}
return doCreate(theResource, null, thePerformIndexing, new Date(), theRequestDetails);
}
}
@ -1325,6 +1344,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
private void validateGivenIdIsAppropriateToRetrieveResource(IIdType theId, BaseHasResource entity) {
if (entity.getForcedId() != null) {
if (myDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY) {
if (theId.isIdPartValidLong()) {
// This means that the resource with the given numeric ID exists, but it has a "forced ID", meaning that
// as far as the outside world is concerned, the given ID doesn't exist (it's just an internal pointer
@ -1334,6 +1354,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
}
}
}
protected void validateOkToDelete(List<DeleteConflict> theDeleteConflicts, ResourceTable theEntity, boolean theForValidate) {
TypedQuery<ResourceLink> query = myEntityManager.createQuery("SELECT l FROM ResourceLink l WHERE l.myTargetResourcePid = :target_pid", ResourceLink.class);

View File

@ -156,6 +156,7 @@ public class DaoConfig {
private List<Integer> mySearchPreFetchThresholds = Arrays.asList(500, 2000, -1);
private List<WarmCacheEntry> myWarmCacheEntries = new ArrayList<>();
private boolean myDisableHashBasedSearches;
private ClientIdStrategyEnum myResourceClientIdStrategy = ClientIdStrategyEnum.ALPHANUMERIC;
/**
* Constructor
@ -642,9 +643,39 @@ public class DaoConfig {
myResourceMetaCountHardLimit = theResourceMetaCountHardLimit;
}
/**
* Controls the behaviour when a client-assigned ID is encountered, i.e. an HTTP PUT
* on a resource ID that does not already exist in the database.
* <p>
* Default is {@link ClientIdStrategyEnum#ALPHANUMERIC}
* </p>
*/
public ClientIdStrategyEnum getResourceClientIdStrategy() {
return myResourceClientIdStrategy;
}
/**
* Controls the behaviour when a client-assigned ID is encountered, i.e. an HTTP PUT
* on a resource ID that does not already exist in the database.
* <p>
* Default is {@link ClientIdStrategyEnum#ALPHANUMERIC}
* </p>
*
* @param theResourceClientIdStrategy Must not be <code>null</code>
*/
public void setResourceClientIdStrategy(ClientIdStrategyEnum theResourceClientIdStrategy) {
Validate.notNull(theResourceClientIdStrategy, "theClientIdStrategy must not be null");
myResourceClientIdStrategy = theResourceClientIdStrategy;
}
/**
* This setting configures the strategy to use in generating IDs for newly
* created resources on the server. The default is {@link IdStrategyEnum#SEQUENTIAL_NUMERIC}.
* <p>
* This strategy is only used for server-assigned IDs, i.e. for HTTP POST
* where the client is requesing that the server store a new resource and give
* it an ID.
* </p>
*/
public IdStrategyEnum getResourceServerIdStrategy() {
return myResourceServerIdStrategy;
@ -653,8 +684,13 @@ public class DaoConfig {
/**
* This setting configures the strategy to use in generating IDs for newly
* created resources on the server. The default is {@link IdStrategyEnum#SEQUENTIAL_NUMERIC}.
* <p>
* This strategy is only used for server-assigned IDs, i.e. for HTTP POST
* where the client is requesing that the server store a new resource and give
* it an ID.
* </p>
*
* @param theResourceIdStrategy The strategy. Must not be null.
* @param theResourceIdStrategy The strategy. Must not be <code>null</code>.
*/
public void setResourceServerIdStrategy(IdStrategyEnum theResourceIdStrategy) {
Validate.notNull(theResourceIdStrategy, "theResourceIdStrategy must not be null");
@ -1353,18 +1389,8 @@ public class DaoConfig {
* given number.
* </p>
*/
public void setSearchPreFetchThresholds(List<Integer> thePreFetchThresholds) {
Validate.isTrue(thePreFetchThresholds.size() > 0, "thePreFetchThresholds must not be empty");
int last = 0;
for (Integer nextInteger : thePreFetchThresholds) {
int nextInt = nextInteger.intValue();
Validate.isTrue(nextInt > 0 || nextInt == -1, nextInt + " is not a valid prefetch threshold");
Validate.isTrue(nextInt != last, "Prefetch thresholds must be sequential");
Validate.isTrue(nextInt > last || nextInt == -1, "Prefetch thresholds must be sequential");
Validate.isTrue(last != -1, "Prefetch thresholds must be sequential");
last = nextInt;
}
mySearchPreFetchThresholds = thePreFetchThresholds;
public List<Integer> getSearchPreFetchThresholds() {
return mySearchPreFetchThresholds;
}
/**
@ -1380,8 +1406,18 @@ public class DaoConfig {
* given number.
* </p>
*/
public List<Integer> getSearchPreFetchThresholds() {
return mySearchPreFetchThresholds;
public void setSearchPreFetchThresholds(List<Integer> thePreFetchThresholds) {
Validate.isTrue(thePreFetchThresholds.size() > 0, "thePreFetchThresholds must not be empty");
int last = 0;
for (Integer nextInteger : thePreFetchThresholds) {
int nextInt = nextInteger.intValue();
Validate.isTrue(nextInt > 0 || nextInt == -1, nextInt + " is not a valid prefetch threshold");
Validate.isTrue(nextInt != last, "Prefetch thresholds must be sequential");
Validate.isTrue(nextInt > last || nextInt == -1, "Prefetch thresholds must be sequential");
Validate.isTrue(last != -1, "Prefetch thresholds must be sequential");
last = nextInt;
}
mySearchPreFetchThresholds = thePreFetchThresholds;
}
/**
@ -1429,6 +1465,36 @@ public class DaoConfig {
UUID
}
public enum ClientIdStrategyEnum {
/**
* Clients are not allowed to supply IDs for resources that do not
* already exist
*/
NOT_ALLOWED,
/**
* Clients may supply IDs but these IDs are not permitted to be purely
* numeric. In other words, values such as "A", "A1" and "000A" would be considered
* valid but "123" would not.
* <p><b>This is the default setting.</b></p>
*/
ALPHANUMERIC,
/**
* Clients may supply any ID including purely numeric IDs. Note that this setting should
* only be set on an empty database, or on a database that has always had this setting
* set as it causes a "forced ID" to be used for all resources.
* <p>
* Note that if you use this setting, it is highly recommended that you also
* set the {@link #setResourceServerIdStrategy(IdStrategyEnum) ResourceServerIdStrategy}
* to {@link IdStrategyEnum#UUID} in order to avoid any potential for conflicts. Otherwise
* a database sequence will be used to generate IDs and these IDs can conflict with
* client-assigned numeric IDs.
* </P>
*/
ANY
}
private static void validateTreatBaseUrlsAsLocal(String theUrl) {
Validate.notBlank(theUrl, "Base URL must not be null or empty");

View File

@ -67,6 +67,9 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
private Boolean ourDisabled;
@Autowired
private DaoConfig myDaoConfig;
/**
* Constructor
*/
@ -222,7 +225,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
StringParam idParm = (StringParam) idParam;
idParamValue = idParm.getValue();
}
pid = BaseHapiFhirDao.translateForcedIdToPid(theResourceName, idParamValue, myForcedIdDao);
pid = BaseHapiFhirDao.translateForcedIdToPid(myDaoConfig, theResourceName, idParamValue, myForcedIdDao);
}
Long referencingPid = pid;
@ -275,7 +278,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
if (contextParts.length != 3 || "Patient".equals(contextParts[0]) == false || "$everything".equals(contextParts[2]) == false) {
throw new InvalidRequestException("Invalid context: " + theContext);
}
Long pid = BaseHapiFhirDao.translateForcedIdToPid(contextParts[0], contextParts[1], myForcedIdDao);
Long pid = BaseHapiFhirDao.translateForcedIdToPid( myDaoConfig, contextParts[0], contextParts[1], myForcedIdDao);
FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager);

View File

@ -1600,7 +1600,7 @@ public class SearchBuilder implements ISearchBuilder {
if (myParams.get(IAnyResource.SP_RES_ID) != null) {
StringParam idParm = (StringParam) myParams.get(IAnyResource.SP_RES_ID).get(0).get(0);
Long pid = BaseHapiFhirDao.translateForcedIdToPid(myResourceName, idParm.getValue(), myForcedIdDao);
Long pid = BaseHapiFhirDao.translateForcedIdToPid(myCallingDao.getConfig(), myResourceName, idParm.getValue(), myForcedIdDao);
if (myAlsoIncludePids == null) {
myAlsoIncludePids = new ArrayList<>(1);
}

View File

@ -46,11 +46,14 @@ public interface IResourceTableDao extends JpaRepository<ResourceTable, Long> {
@Query("SELECT t.myResourceType as type, COUNT(t.myResourceType) as count FROM ResourceTable t GROUP BY t.myResourceType")
List<Map<?, ?>> getResourceCounts();
@Query("SELECT t.myId FROM ResourceTable t WHERE t.myUpdated >= :low AND t.myUpdated <= :high ORDER BY t.myUpdated DESC")
Slice<Long> findIdsOfResourcesWithinUpdatedRangeOrderedFromNewest(Pageable thePage, @Param("low") Date theLow, @Param("high") Date theHigh);
@Query("SELECT t.myId FROM ResourceTable t WHERE t.myUpdated >= :low AND t.myUpdated <= :high ORDER BY t.myUpdated ASC")
Slice<Long> findIdsOfResourcesWithinUpdatedRangeOrderedFromOldest(Pageable thePage,@Param("low") Date theLow, @Param("high")Date theHigh);
Slice<Long> findIdsOfResourcesWithinUpdatedRangeOrderedFromOldest(Pageable thePage, @Param("low") Date theLow, @Param("high") Date theHigh);
@Query("SELECT t.myId FROM ResourceTable t WHERE t.myUpdated >= :low AND t.myUpdated <= :high AND t.myResourceType = :restype ORDER BY t.myUpdated ASC")
Slice<Long> findIdsOfResourcesWithinUpdatedRangeOrderedFromOldest(Pageable thePage,@Param("restype") String theResourceType, @Param("low") Date theLow, @Param("high")Date theHigh);
Slice<Long> findIdsOfResourcesWithinUpdatedRangeOrderedFromOldest(Pageable thePage, @Param("restype") String theResourceType, @Param("low") Date theLow, @Param("high") Date theHigh);
@Modifying
@Query("UPDATE ResourceTable t SET t.myIndexStatus = :status WHERE t.myId = :id")

View File

@ -45,6 +45,10 @@ public interface ISearchResultDao extends JpaRepository<SearchResult, Long> {
@Query(value="SELECT r.myId FROM SearchResult r WHERE r.mySearchPid = :search")
Slice<Long> findForSearch(Pageable thePage, @Param("search") Long theSearchPid);
@Modifying
@Query("DELETE FROM SearchResult s WHERE s.myResourcePid IN :ids")
void deleteByResourceIds(@Param("ids") List<Long> theContent);
@Modifying
@Query("DELETE FROM SearchResult s WHERE s.myId IN :ids")
void deleteByIds(@Param("ids") List<Long> theContent);

View File

@ -44,7 +44,7 @@ public interface IResourceReindexingSvc {
* Does the same thing as {@link #runReindexingPass()} but makes sure to perform at
* least one pass even if one is half finished
*/
Integer forceReindexingPass();
int forceReindexingPass();
/**
* Cancels all running and future reindexing jobs. This is mainly intended

View File

@ -196,7 +196,7 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
}
@Override
public Integer forceReindexingPass() {
public int forceReindexingPass() {
myIndexingLock.lock();
try {
return doReindexingPassInsideLock();
@ -219,7 +219,7 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
expungeJobsMarkedAsDeleted();
}
private Integer runReindexJobs() {
private int runReindexJobs() {
Collection<ResourceReindexJobEntity> jobs = myTxTemplate.execute(t -> myReindexJobDao.findAll(PageRequest.of(0, 10), false));
assert jobs != null;

View File

@ -31,7 +31,7 @@ public class FhirResourceDaoR4CodeSystemTest extends BaseJpaR4Test {
myCodeSystemDao.create(cs, mySrd);
myResourceReindexingSvc.markAllResourcesForReindexing();
int outcome = myResourceReindexingSvc.runReindexingPass();
int outcome = myResourceReindexingSvc.forceReindexingPass();
assertNotEquals(-1, outcome); // -1 means there was a failure
myTermSvc.saveDeferred();

View File

@ -3,7 +3,9 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*;
import org.junit.After;
@ -11,12 +13,14 @@ import org.junit.AfterClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.PageRequest;
import java.io.IOException;
import java.util.Date;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.*;
public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4CreateTest.class);
@ -24,6 +28,7 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
@After
public void afterResetDao() {
myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy());
myDaoConfig.setResourceClientIdStrategy(new DaoConfig().getResourceClientIdStrategy());
}
@Test
@ -69,6 +74,134 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
}
@Test
public void testCreateWithClientAssignedIdDisallowed() {
myDaoConfig.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.NOT_ALLOWED);
Patient p = new Patient();
p.setId("AAA");
p.addName().setFamily("FAM");
try {
myPatientDao.update(p);
fail();
} catch (ResourceNotFoundException e) {
assertEquals("No resource exists on this server resource with ID[AAA], and client-assigned IDs are not enabled.", e.getMessage());
}
}
@Test
public void testCreateWithClientAssignedIdPureNumeric() {
myDaoConfig.setResourceServerIdStrategy(DaoConfig.IdStrategyEnum.SEQUENTIAL_NUMERIC);
myDaoConfig.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.ANY);
// Create a server assigned ID
Patient p = new Patient();
p.setActive(true);
IIdType id0 = myPatientDao.create(p).getId();
long firstClientAssignedId = id0.getIdPartAsLong();
long newId = firstClientAssignedId + 2L;
// Read it back
p = myPatientDao.read(new IdType("Patient/" + firstClientAssignedId));
assertEquals(true, p.getActive());
// Not create a client assigned numeric ID
p = new Patient();
p.setId("Patient/" + newId);
p.addName().setFamily("FAM");
IIdType id1 = myPatientDao.update(p).getId();
assertEquals(Long.toString(newId), id1.getIdPart());
assertEquals("1", id1.getVersionIdPart());
p = myPatientDao.read(id1);
assertEquals("FAM", p.getNameFirstRep().getFamily());
// Update it
p = new Patient();
p.setId("Patient/" + newId);
p.addName().setFamily("FAM2");
id1 = myPatientDao.update(p).getId();
assertEquals(Long.toString(newId), id1.getIdPart());
assertEquals("2", id1.getVersionIdPart());
p = myPatientDao.read(id1);
assertEquals("FAM2", p.getNameFirstRep().getFamily());
// Try to create another server-assigned. This should fail since we have a
// a conflict.
p = new Patient();
p.setActive(false);
try {
myPatientDao.create(p).getId();
fail();
} catch (DataIntegrityViolationException e) {
// good
}
ourLog.info("ID0: {}", id0);
ourLog.info("ID1: {}", id1);
}
@Test
public void testCreateWithClientAssignedIdPureNumericServerIdUuid() {
myDaoConfig.setResourceServerIdStrategy(DaoConfig.IdStrategyEnum.UUID);
myDaoConfig.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.ANY);
// Create a server assigned ID
Patient p = new Patient();
p.setActive(true);
IIdType id0 = myPatientDao.create(p).getId();
// Read it back
p = myPatientDao.read(id0.toUnqualifiedVersionless());
assertEquals(true, p.getActive());
// Pick an ID that was already used as an internal PID
Long newId = runInTransaction(() -> myResourceTableDao.findIdsOfResourcesWithinUpdatedRangeOrderedFromNewest(
PageRequest.of(0, 1),
DateUtils.addDays(new Date(), -1),
DateUtils.addDays(new Date(), 1)
).getContent().get(0));
// Not create a client assigned numeric ID
p = new Patient();
p.setId("Patient/" + newId);
p.addName().setFamily("FAM");
IIdType id1 = myPatientDao.update(p).getId();
assertEquals(Long.toString(newId), id1.getIdPart());
assertEquals("1", id1.getVersionIdPart());
// Read it back
p = myPatientDao.read(id1);
assertEquals("FAM", p.getNameFirstRep().getFamily());
// Update it
p = new Patient();
p.setId("Patient/" + newId);
p.addName().setFamily("FAM2");
id1 = myPatientDao.update(p).getId();
assertEquals(Long.toString(newId), id1.getIdPart());
assertEquals("2", id1.getVersionIdPart());
p = myPatientDao.read(id1);
assertEquals("FAM2", p.getNameFirstRep().getFamily());
// Try to create another server-assigned. This should fail since we have a
// a conflict.
p = new Patient();
p.setActive(false);
IIdType id2 = myPatientDao.create(p).getId();
ourLog.info("ID0: {}", id0);
ourLog.info("ID1: {}", id1);
ourLog.info("ID2: {}", id2);
}
@Test
public void testTransactionCreateWithUuidResourceStrategy() {
myDaoConfig.setResourceServerIdStrategy(DaoConfig.IdStrategyEnum.UUID);

View File

@ -146,8 +146,8 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
mySearchParameterDao.create(fooSp, mySrd);
assertEquals(1, myResourceReindexingSvc.forceReindexingPass().intValue());
assertEquals(0, myResourceReindexingSvc.forceReindexingPass().intValue());
assertEquals(1, myResourceReindexingSvc.forceReindexingPass());
assertEquals(0, myResourceReindexingSvc.forceReindexingPass());
}

View File

@ -182,7 +182,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
});
myResourceReindexingSvc.markAllResourcesForReindexing();
myResourceReindexingSvc.runReindexingPass();
myResourceReindexingSvc.forceReindexingPass();
runInTransaction(() -> {
Optional<ResourceTable> tableOpt = myResourceTableDao.findById(id1.getIdPartAsLong());
@ -225,7 +225,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
});
myResourceReindexingSvc.markAllResourcesForReindexing();
myResourceReindexingSvc.runReindexingPass();
myResourceReindexingSvc.forceReindexingPass();
runInTransaction(() -> {
Optional<ResourceTable> tableOpt = myResourceTableDao.findById(id1.getIdPartAsLong());

View File

@ -2,15 +2,14 @@ package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.util.ExpungeOptions;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.TestUtil;
import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Observation;
@ -19,7 +18,8 @@ import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.not;
@ -168,17 +168,17 @@ public class ExpungeR4Test extends BaseResourceProviderR4Test {
myPatientDao.update(p).getId();
myPatientDao.delete(new IdType("Patient/TEST"));
runInTransaction(()-> assertThat(myResourceTableDao.findAll(), not(empty())));
runInTransaction(()-> assertThat(myResourceHistoryTableDao.findAll(), not(empty())));
runInTransaction(()-> assertThat(myForcedIdDao.findAll(), not(empty())));
runInTransaction(() -> assertThat(myResourceTableDao.findAll(), not(empty())));
runInTransaction(() -> assertThat(myResourceHistoryTableDao.findAll(), not(empty())));
runInTransaction(() -> assertThat(myForcedIdDao.findAll(), not(empty())));
myPatientDao.expunge(new ExpungeOptions()
.setExpungeDeletedResources(true)
.setExpungeOldVersions(true));
runInTransaction(()-> assertThat(myResourceTableDao.findAll(), empty()));
runInTransaction(()-> assertThat(myResourceHistoryTableDao.findAll(), empty()));
runInTransaction(()-> assertThat(myForcedIdDao.findAll(), empty()));
runInTransaction(() -> assertThat(myResourceTableDao.findAll(), empty()));
runInTransaction(() -> assertThat(myResourceHistoryTableDao.findAll(), empty()));
runInTransaction(() -> assertThat(myForcedIdDao.findAll(), empty()));
}
@ -332,6 +332,61 @@ public class ExpungeR4Test extends BaseResourceProviderR4Test {
assertGone(myDeletedObservationId);
}
@Test
public void testExpungeEverythingWhereResourceInSearchResults() {
createStandardPatients();
IBundleProvider search = myPatientDao.search(new SearchParameterMap());
assertEquals(2, search.size().intValue());
search.getResources(0, 2);
runInTransaction(() -> {
assertEquals(2, mySearchResultDao.count());
});
mySystemDao.expunge(new ExpungeOptions()
.setExpungeEverything(true));
// Everything deleted
assertExpunged(myOneVersionPatientId);
assertExpunged(myTwoVersionPatientId.withVersion("1"));
assertExpunged(myTwoVersionPatientId.withVersion("2"));
assertExpunged(myDeletedPatientId.withVersion("1"));
assertExpunged(myDeletedPatientId);
// Everything deleted
assertExpunged(myOneVersionObservationId);
assertExpunged(myTwoVersionObservationId.withVersion("1"));
assertExpunged(myTwoVersionObservationId.withVersion("2"));
assertExpunged(myDeletedObservationId);
}
@Test
public void testExpungeDeletedWhereResourceInSearchResults() {
createStandardPatients();
IBundleProvider search = myPatientDao.search(new SearchParameterMap());
assertEquals(2, search.size().intValue());
List<IBaseResource> resources = search.getResources(0, 2);
myPatientDao.delete(resources.get(0).getIdElement());
runInTransaction(() -> {
assertEquals(2, mySearchResultDao.count());
});
mySystemDao.expunge(new ExpungeOptions()
.setExpungeDeletedResources(true));
// Everything deleted
assertExpunged(myOneVersionPatientId);
assertStillThere(myTwoVersionPatientId.withVersion("1"));
assertStillThere(myTwoVersionPatientId.withVersion("2"));
assertExpunged(myDeletedPatientId.withVersion("1"));
assertExpunged(myDeletedPatientId);
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();

View File

@ -24,6 +24,7 @@ import org.junit.*;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertEquals;
@ -35,14 +36,14 @@ import static org.junit.Assert.fail;
public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestDstu3Test.class);
private static List<Observation> ourCreatedObservations = Lists.newArrayList();
private static List<Observation> ourCreatedObservations = Collections.synchronizedList(Lists.newArrayList());
private static int ourListenerPort;
private static RestfulServer ourListenerRestServer;
private static Server ourListenerServer;
private static String ourListenerServerBase;
private static List<Observation> ourUpdatedObservations = Lists.newArrayList();
private static List<String> ourContentTypes = new ArrayList<>();
private List<IIdType> mySubscriptionIds = new ArrayList<>();
private static List<Observation> ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
private static List<String> ourContentTypes = Collections.synchronizedList(new ArrayList<>());
private List<IIdType> mySubscriptionIds = Collections.synchronizedList(new ArrayList<>());
@After
public void afterUnregisterRestHookListener() {

View File

@ -1,4 +1,3 @@
package ca.uhn.fhir.jpa.subscription;
import ca.uhn.fhir.context.FhirContext;
@ -27,6 +26,7 @@ import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.*;
import java.util.Collections;
import java.util.List;
/**
@ -34,13 +34,13 @@ import java.util.List;
*/
public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends BaseResourceProviderDstu2Test {
private static List<Observation> ourCreatedObservations = Lists.newArrayList();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.class);
private static List<Observation> ourCreatedObservations = Collections.synchronizedList(Lists.newArrayList());
private static int ourListenerPort;
private static RestfulServer ourListenerRestServer;
private static Server ourListenerServer;
private static String ourListenerServerBase;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.class);
private static List<Observation> ourUpdatedObservations = Lists.newArrayList();
private static List<Observation> ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
@After
public void afterUnregisterRestHookListener() {
@ -142,7 +142,7 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B
waitForSize(0, ourCreatedObservations);
waitForSize(3, ourUpdatedObservations);
ourClient.delete().resourceById(new IdDt("Subscription/"+ subscription2.getId())).execute();
ourClient.delete().resourceById(new IdDt("Subscription/" + subscription2.getId())).execute();
Observation observationTemp3 = sendObservation(code, "SNOMED-CT");
@ -215,7 +215,7 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B
waitForSize(0, ourCreatedObservations);
waitForSize(3, ourUpdatedObservations);
ourClient.delete().resourceById(new IdDt("Subscription/"+ subscription2.getId())).execute();
ourClient.delete().resourceById(new IdDt("Subscription/" + subscription2.getId())).execute();
Observation observationTemp3 = sendObservation(code, "SNOMED-CT");
@ -256,6 +256,28 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B
Assert.assertFalse(observation2.getId().isEmpty());
}
public static class ObservationListener implements IResourceProvider {
@Create
public MethodOutcome create(@ResourceParam Observation theObservation) {
ourLog.info("Received Listener Create");
ourCreatedObservations.add(theObservation);
return new MethodOutcome(new IdDt("Observation/1"), true);
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return Observation.class;
}
@Update
public MethodOutcome update(@ResourceParam Observation theObservation) {
ourLog.info("Received Listener Update");
ourUpdatedObservations.add(theObservation);
return new MethodOutcome(new IdDt("Observation/1"), false);
}
}
@BeforeClass
public static void startListenerServer() throws Exception {
@ -284,27 +306,4 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B
ourListenerServer.stop();
}
public static class ObservationListener implements IResourceProvider {
@Create
public MethodOutcome create(@ResourceParam Observation theObservation) {
ourLog.info("Received Listener Create");
ourCreatedObservations.add(theObservation);
return new MethodOutcome(new IdDt("Observation/1"), true);
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return Observation.class;
}
@Update
public MethodOutcome update(@ResourceParam Observation theObservation) {
ourLog.info("Received Listener Update");
ourUpdatedObservations.add(theObservation);
return new MethodOutcome(new IdDt("Observation/1"), false);
}
}
}

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.subscription;
import static org.junit.Assert.*;
import java.util.Collections;
import java.util.List;
import org.eclipse.jetty.server.Server;
@ -29,13 +30,13 @@ import ca.uhn.fhir.rest.server.RestfulServer;
*/
public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test extends BaseResourceProviderDstu3Test {
private static List<Observation> ourCreatedObservations = Lists.newArrayList();
private static List<Observation> ourCreatedObservations = Collections.synchronizedList(Lists.newArrayList());
private static int ourListenerPort;
private static RestfulServer ourListenerRestServer;
private static Server ourListenerServer;
private static String ourListenerServerBase;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.class);
private static List<Observation> ourUpdatedObservations = Lists.newArrayList();
private static List<Observation> ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
@Override
protected boolean shouldLogClient() {

View File

@ -23,10 +23,12 @@ import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourceProviderR4Test {
@ -35,9 +37,9 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc
private static RestfulServer ourListenerRestServer;
private static String ourListenerServerBase;
private static Server ourListenerServer;
private static List<Observation> ourUpdatedObservations = Lists.newArrayList();
private static List<String> ourContentTypes = new ArrayList<>();
private static List<String> ourHeaders = new ArrayList<>();
private static List<Observation> ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
private static List<String> ourContentTypes = Collections.synchronizedList(new ArrayList<>());
private static List<String> ourHeaders = Collections.synchronizedList(new ArrayList<>());
@After
public void afterResetSubscriptionActivatingInterceptor() {
@ -123,33 +125,6 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc
}
}
@BeforeClass
public static void startListenerServer() throws Exception {
ourListenerPort = PortUtil.findFreePort();
ourListenerRestServer = new RestfulServer(FhirContext.forR4());
ourListenerServerBase = "http://localhost:" + ourListenerPort + "/fhir/context";
ObservationListener obsListener = new ObservationListener();
ourListenerRestServer.setResourceProviders(obsListener);
ourListenerServer = new Server(ourListenerPort);
ServletContextHandler proxyHandler = new ServletContextHandler();
proxyHandler.setContextPath("/");
ServletHolder servletHolder = new ServletHolder();
servletHolder.setServlet(ourListenerRestServer);
proxyHandler.addServlet(servletHolder, "/fhir/context/*");
ourListenerServer.setHandler(proxyHandler);
ourListenerServer.start();
}
@AfterClass
public static void stopListenerServer() throws Exception {
ourListenerServer.stop();
}
public static class ObservationListener implements IResourceProvider {
@ -181,4 +156,31 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc
}
@BeforeClass
public static void startListenerServer() throws Exception {
ourListenerPort = PortUtil.findFreePort();
ourListenerRestServer = new RestfulServer(FhirContext.forR4());
ourListenerServerBase = "http://localhost:" + ourListenerPort + "/fhir/context";
ObservationListener obsListener = new ObservationListener();
ourListenerRestServer.setResourceProviders(obsListener);
ourListenerServer = new Server(ourListenerPort);
ServletContextHandler proxyHandler = new ServletContextHandler();
proxyHandler.setContextPath("/");
ServletHolder servletHolder = new ServletHolder();
servletHolder.setServlet(ourListenerRestServer);
proxyHandler.addServlet(servletHolder, "/fhir/context/*");
ourListenerServer.setHandler(proxyHandler);
ourListenerServer.start();
}
@AfterClass
public static void stopListenerServer() throws Exception {
ourListenerServer.stop();
}
}

View File

@ -27,6 +27,7 @@ import org.springframework.messaging.support.ExecutorSubscribableChannel;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
@ -41,15 +42,15 @@ import static org.junit.Assert.fail;
public class RestHookTestR4Test extends BaseResourceProviderR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestDstu2Test.class);
private static List<Observation> ourCreatedObservations = Lists.newArrayList();
private static List<Observation> ourCreatedObservations = Collections.synchronizedList(Lists.newArrayList());
private static int ourListenerPort;
private static RestfulServer ourListenerRestServer;
private static Server ourListenerServer;
private static String ourListenerServerBase;
private static List<Observation> ourUpdatedObservations = Lists.newArrayList();
private static List<String> ourContentTypes = new ArrayList<>();
private static List<String> ourHeaders = new ArrayList<>();
private List<IIdType> mySubscriptionIds = new ArrayList<>();
private static List<Observation> ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
private static List<String> ourContentTypes = Collections.synchronizedList(new ArrayList<>());
private static List<String> ourHeaders = Collections.synchronizedList(new ArrayList<>());
private List<IIdType> mySubscriptionIds = Collections.synchronizedList(new ArrayList<>());
private CountingInterceptor myCountingInterceptor;
@After

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.subscription.r4;
import java.util.Collections;
import java.util.List;
import org.eclipse.jetty.server.Server;
@ -27,13 +28,13 @@ import ca.uhn.fhir.rest.server.RestfulServer;
*/
public class RestHookTestWithInterceptorRegisteredToDaoConfigR4Test extends BaseResourceProviderR4Test {
private static List<Observation> ourCreatedObservations = Lists.newArrayList();
private static List<Observation> ourCreatedObservations = Collections.synchronizedList(Lists.newArrayList());
private static int ourListenerPort;
private static RestfulServer ourListenerRestServer;
private static Server ourListenerServer;
private static String ourListenerServerBase;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.class);
private static List<Observation> ourUpdatedObservations = Lists.newArrayList();
private static List<Observation> ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
@Override
protected boolean shouldLogClient() {

View File

@ -13,6 +13,7 @@ import org.junit.Test;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
@ -35,13 +36,13 @@ import java.util.List;
public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Test {
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookWithEventDefinitionR4Test.class);
private static List<Observation> ourUpdatedObservations = Lists.newArrayList();
private static List<String> ourContentTypes = new ArrayList<>();
private static List<String> ourHeaders = new ArrayList<>();
private static List<Observation> ourCreatedObservations = Lists.newArrayList();
private static List<Observation> ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
private static List<String> ourContentTypes = Collections.synchronizedList(new ArrayList<>());
private static List<String> ourHeaders = Collections.synchronizedList(new ArrayList<>());
private static List<Observation> ourCreatedObservations = Collections.synchronizedList(Lists.newArrayList());
private String myPatientId;
private String mySubscriptionId;
private List<IIdType> mySubscriptionIds = new ArrayList<>();
private List<IIdType> mySubscriptionIds = Collections.synchronizedList(new ArrayList<>());
@Override
@After

View File

@ -83,15 +83,12 @@ public class ServerExceptionDstu3Test {
ourException = new InternalErrorException("Error", operationOutcome);
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_format=json");
CloseableHttpResponse status = ourClient.execute(httpGet);
try {
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
byte[] responseContentBytes = IOUtils.toByteArray(status.getEntity().getContent());
String responseContent = new String(responseContentBytes, Charsets.UTF_8);
ourLog.info(status.getStatusLine().toString());
ourLog.info(responseContent);
assertThat(responseContent, containsString("El nombre está vacío"));
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
}
@ -105,16 +102,13 @@ public class ServerExceptionDstu3Test {
ourException = new AuthenticationException().addAuthenticateHeaderForRealm("REALM");
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
CloseableHttpResponse status = ourClient.execute(httpGet);
try {
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(status.getStatusLine().toString());
ourLog.info(responseContent);
assertEquals(401, status.getStatusLine().getStatusCode());
assertEquals("Basic realm=\"REALM\"", status.getFirstHeader("WWW-Authenticate").getValue());
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
}

View File

@ -2063,7 +2063,7 @@
</reportsDirectories>
</configuration>
</plugin>
<plugin>
<!--<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
@ -2078,7 +2078,7 @@
</reports>
</reportSet>
</reportSets>
</plugin>
</plugin>-->
<!-- <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-linkcheck-plugin</artifactId> <version>1.1</version> </plugin> -->
</plugins>
</reporting>

View File

@ -147,6 +147,23 @@
The JPA server version migrator tool now runs in a multithreaded way, allowing it to
upgrade th database faster when migration tasks require data updates.
</action>
<action type="fix">
A bug in the JPA server was fixed: When a resource was previously deleted,
a transaction could not be posted that both restored the deleted resource but
also contained references to the now-restored resource.
</action>
<action type="fix">
The $expunge operation could sometimes fail to delete resources if a resource
to be deleted had recently been returned in a search result. This has been
corrected.
</action>
<action type="add">
A new setting has been added to the JPA Server DopConfig that controls the
behaviour when a client-assigned ID is encountered (i.e. the client performs
an HTTP PUT to a resource ID that doesn't already exist on the server). It is
now possible to disallow this action, to only allow alphanumeric IDs (the default
and only option previously) or allow any IDs including alphanumeric.
</action>
</release>
<release version="3.5.0" date="2018-09-17">