Work on subscription cleanup
This commit is contained in:
parent
d0884663d2
commit
40d0c27ae3
|
@ -51,7 +51,15 @@ public class DaoRegistry implements ApplicationContextAware, IDaoRegistry {
|
|||
* Constructor
|
||||
*/
|
||||
public DaoRegistry() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public DaoRegistry(FhirContext theFhirContext) {
|
||||
super();
|
||||
myContext = theFhirContext;
|
||||
}
|
||||
|
||||
public void setSupportedResourceTypes(Collection<String> theSupportedResourceTypes) {
|
||||
|
|
|
@ -163,7 +163,6 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
|
|||
mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class);
|
||||
ourSearchParamRegistry = wac.getBean(SearchParamRegistryImpl.class);
|
||||
ourSubscriptionMatcherInterceptor = wac.getBean(SubscriptionMatcherInterceptor.class);
|
||||
ourSubscriptionMatcherInterceptor.start();
|
||||
|
||||
confProvider.setSearchParamRegistry(ourSearchParamRegistry);
|
||||
|
||||
|
|
|
@ -166,7 +166,6 @@ public abstract class BaseResourceProviderR5Test extends BaseJpaR5Test {
|
|||
mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class);
|
||||
ourSearchParamRegistry = wac.getBean(SearchParamRegistryImpl.class);
|
||||
ourSubscriptionMatcherInterceptor = wac.getBean(SubscriptionMatcherInterceptor.class);
|
||||
ourSubscriptionMatcherInterceptor.start();
|
||||
|
||||
myFhirCtx.getRestfulClientFactory().setSocketTimeout(5000000);
|
||||
confProvider.setSearchParamRegistry(ourSearchParamRegistry);
|
||||
|
|
|
@ -33,13 +33,13 @@ public class SubscriptionChannelConfig {
|
|||
* Create a @Primary @Bean if you need a different implementation
|
||||
*/
|
||||
@Bean
|
||||
public IQueueChannelFactory subscribableChannelFactory() {
|
||||
public IQueueChannelFactory queueChannelFactory() {
|
||||
return new LinkedBlockingQueueChannelFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SubscriptionChannelFactory subscriptionChannelFactory() {
|
||||
return new SubscriptionChannelFactory();
|
||||
public SubscriptionChannelFactory subscriptionChannelFactory(IQueueChannelFactory theQueueChannelFactory) {
|
||||
return new SubscriptionChannelFactory(theQueueChannelFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,11 +21,13 @@ package ca.uhn.fhir.jpa.subscription.channel.subscription;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.channel.queue.IQueueChannelFactory;
|
||||
import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage;
|
||||
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage;
|
||||
import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryJsonMessage;
|
||||
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage;
|
||||
import ca.uhn.fhir.jpa.subscription.process.registry.SubscriptionConstants;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
|
@ -35,24 +37,32 @@ import org.springframework.messaging.support.AbstractSubscribableChannel;
|
|||
|
||||
public class SubscriptionChannelFactory {
|
||||
|
||||
@Autowired
|
||||
private IQueueChannelFactory mySubscribableChannelFactory;
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionChannelFactory.class);
|
||||
private final IQueueChannelFactory myQueueChannelFactory;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public SubscriptionChannelFactory(IQueueChannelFactory theQueueChannelFactory) {
|
||||
Validate.notNull(theQueueChannelFactory);
|
||||
myQueueChannelFactory = theQueueChannelFactory;
|
||||
}
|
||||
|
||||
public MessageChannel newDeliverySendingChannel(String theChannelName) {
|
||||
return mySubscribableChannelFactory.getOrCreateSender(theChannelName, ResourceDeliveryMessage.class, getDeliveryChannelConcurrentConsumers());
|
||||
return myQueueChannelFactory.getOrCreateSender(theChannelName, ResourceDeliveryJsonMessage.class, getDeliveryChannelConcurrentConsumers());
|
||||
}
|
||||
|
||||
public SubscribableChannel newDeliveryChannel(String theChannelName) {
|
||||
SubscribableChannel channel = mySubscribableChannelFactory.getOrCreateReceiver(theChannelName, ResourceDeliveryMessage.class, getDeliveryChannelConcurrentConsumers());
|
||||
SubscribableChannel channel = myQueueChannelFactory.getOrCreateReceiver(theChannelName, ResourceDeliveryJsonMessage.class, getDeliveryChannelConcurrentConsumers());
|
||||
return new BroadcastingSubscribableChannelWrapper(channel);
|
||||
}
|
||||
|
||||
public MessageChannel newMatchingSendingChannel(String theChannelName) {
|
||||
return mySubscribableChannelFactory.getOrCreateSender(theChannelName, ResourceModifiedMessage.class, getMatchingChannelConcurrentConsumers());
|
||||
return myQueueChannelFactory.getOrCreateSender(theChannelName, ResourceModifiedJsonMessage.class, getMatchingChannelConcurrentConsumers());
|
||||
}
|
||||
|
||||
public SubscribableChannel newMatchingReceivingChannel(String theChannelName) {
|
||||
SubscribableChannel channel = mySubscribableChannelFactory.getOrCreateReceiver(theChannelName, ResourceModifiedMessage.class, getMatchingChannelConcurrentConsumers());
|
||||
SubscribableChannel channel = myQueueChannelFactory.getOrCreateReceiver(theChannelName, ResourceModifiedJsonMessage.class, getMatchingChannelConcurrentConsumers());
|
||||
return new BroadcastingSubscribableChannelWrapper(channel);
|
||||
}
|
||||
|
||||
|
@ -64,8 +74,7 @@ public class SubscriptionChannelFactory {
|
|||
return SubscriptionConstants.MATCHING_CHANNEL_CONCURRENT_CONSUMERS;
|
||||
}
|
||||
|
||||
|
||||
private static class BroadcastingSubscribableChannelWrapper extends AbstractSubscribableChannel implements MessageHandler, DisposableBean {
|
||||
public static class BroadcastingSubscribableChannelWrapper extends AbstractSubscribableChannel implements MessageHandler, DisposableBean {
|
||||
|
||||
private final SubscribableChannel myWrappedChannel;
|
||||
|
||||
|
@ -74,13 +83,21 @@ public class SubscriptionChannelFactory {
|
|||
myWrappedChannel = theChannel;
|
||||
}
|
||||
|
||||
public SubscribableChannel getWrappedChannel() {
|
||||
return myWrappedChannel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean sendInternal(Message<?> theMessage, long timeout) {
|
||||
for (MessageHandler next : getSubscribers()) {
|
||||
next.handleMessage(theMessage);
|
||||
}
|
||||
return true;
|
||||
// try {
|
||||
for (MessageHandler next : getSubscribers()) {
|
||||
next.handleMessage(theMessage);
|
||||
}
|
||||
return true;
|
||||
// } catch (Exception e) {
|
||||
// ourLog.error("Failiure handling message", e);
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -94,7 +111,6 @@ public class SubscriptionChannelFactory {
|
|||
((DisposableBean) myWrappedChannel).destroy();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -87,9 +87,9 @@ public class SubscriptionChannelRegistry {
|
|||
channel.close();
|
||||
}
|
||||
myDeliveryReceiverChannels.closeAndRemove(channelName);
|
||||
myChannelNameToSender.remove(channelName);
|
||||
}
|
||||
|
||||
myChannelNameToSender.remove(channelName);
|
||||
}
|
||||
|
||||
public synchronized SubscriptionChannelWithHandlers getDeliveryReceiverChannel(String theChannelName) {
|
||||
|
|
|
@ -26,6 +26,7 @@ import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
|||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.subscription.process.registry.ActiveSubscription;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -35,10 +36,25 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
public class DaoResourceRetriever implements IResourceRetriever {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(ActiveSubscription.class);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public DaoResourceRetriever() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public DaoResourceRetriever(FhirContext theFhirContext, DaoRegistry theDaoRegistry) {
|
||||
myFhirContext = theFhirContext;
|
||||
myDaoRegistry = theDaoRegistry;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
FhirContext myFhirContext;
|
||||
private FhirContext myFhirContext;
|
||||
@Autowired
|
||||
DaoRegistry myDaoRegistry;
|
||||
private DaoRegistry myDaoRegistry;
|
||||
|
||||
@Override
|
||||
public IBaseResource getResource(IIdType payloadId) throws ResourceGoneException {
|
||||
|
|
|
@ -23,18 +23,21 @@ package ca.uhn.fhir.jpa.subscription.process.matcher.subscriber;
|
|||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
public abstract class BaseSubscriberForSubscriptionResources implements MessageHandler {
|
||||
|
||||
@Autowired
|
||||
protected FhirContext myFhirContext;
|
||||
|
||||
protected boolean isSubscription(ResourceModifiedMessage theNewResource) {
|
||||
IIdType payloadId = theNewResource.getId(myFhirContext);
|
||||
String payloadIdType = payloadId.getResourceType();
|
||||
IBaseResource payload = theNewResource.getNewPayload(myFhirContext);
|
||||
String payloadIdType = myFhirContext.getResourceDefinition(payload).getName();
|
||||
return payloadIdType.equals(ResourceTypeEnum.SUBSCRIPTION.getCode());
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,9 @@ import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelFact
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.context.event.ContextStartedEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
|
||||
|
@ -46,8 +49,8 @@ public class MatchingQueueSubscriberLoader {
|
|||
|
||||
protected SubscribableChannel myMatchingChannel;
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
@EventListener(classes = {ContextRefreshedEvent.class})
|
||||
public void handleContextRefreshEvent() {
|
||||
if (myMatchingChannel == null) {
|
||||
myMatchingChannel = mySubscriptionChannelFactory.newMatchingReceivingChannel(SUBSCRIPTION_MATCHING_CHANNEL_NAME);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
@ -56,7 +58,7 @@ public class SubscriptionMatcherInterceptor implements IResourceModifiedConsumer
|
|||
@Autowired
|
||||
private SubscriptionChannelFactory mySubscriptionChannelFactory;
|
||||
|
||||
private MessageChannel myMatchingChannel;
|
||||
private volatile MessageChannel myMatchingChannel;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -65,23 +67,28 @@ public class SubscriptionMatcherInterceptor implements IResourceModifiedConsumer
|
|||
super();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
myMatchingChannel = mySubscriptionChannelFactory.newMatchingSendingChannel(SubscriptionMatchingSubscriber.SUBSCRIPTION_MATCHING_CHANNEL_NAME);
|
||||
@EventListener(classes = {ContextRefreshedEvent.class})
|
||||
public void startIfNeeded() {
|
||||
if (myMatchingChannel == null) {
|
||||
myMatchingChannel = mySubscriptionChannelFactory.newMatchingSendingChannel(SubscriptionMatchingSubscriber.SUBSCRIPTION_MATCHING_CHANNEL_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
@Hook(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED)
|
||||
public void resourceCreated(IBaseResource theResource, RequestDetails theRequest) {
|
||||
startIfNeeded();
|
||||
submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.CREATE, theRequest);
|
||||
}
|
||||
|
||||
@Hook(Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED)
|
||||
public void resourceDeleted(IBaseResource theResource, RequestDetails theRequest) {
|
||||
startIfNeeded();
|
||||
submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.DELETE, theRequest);
|
||||
}
|
||||
|
||||
@Hook(Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED)
|
||||
public void resourceUpdated(IBaseResource theOldResource, IBaseResource theNewResource, RequestDetails theRequest) {
|
||||
startIfNeeded();
|
||||
submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE, theRequest);
|
||||
}
|
||||
|
||||
|
|
|
@ -53,7 +53,6 @@ public class SubscriptionSubmitInterceptorLoader {
|
|||
if (supportedSubscriptionTypes.isEmpty()) {
|
||||
ourLog.info("Subscriptions are disabled on this server. Subscriptions will not be activated and incoming resources will not be matched against subscriptions.");
|
||||
} else {
|
||||
mySubscriptionMatcherInterceptor.start();
|
||||
ourLog.info("Registering subscription matcher interceptor");
|
||||
myInterceptorRegistry.registerInterceptor(mySubscriptionMatcherInterceptor);
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
|||
@Configuration
|
||||
@Import({SearchParamConfig.class})
|
||||
@EnableScheduling
|
||||
public class SubscriptionConfig {
|
||||
public class SubscriptionTestConfig {
|
||||
|
||||
@Autowired
|
||||
private FhirContext myFhirContext;
|
||||
|
@ -54,8 +54,8 @@ public class SubscriptionConfig {
|
|||
}
|
||||
|
||||
@Bean
|
||||
public SubscriptionChannelFactory subscriptionChannelFactory() {
|
||||
return new SubscriptionChannelFactory();
|
||||
public SubscriptionChannelFactory subscriptionChannelFactory(IQueueChannelFactory theQueueChannelFactory) {
|
||||
return new SubscriptionChannelFactory(theQueueChannelFactory);
|
||||
}
|
||||
|
||||
|
|
@ -31,7 +31,11 @@ import ca.uhn.fhir.model.api.IResource;
|
|||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.api.server.*;
|
||||
import ca.uhn.fhir.rest.api.server.IPreResourceAccessDetails;
|
||||
import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.SimplePreResourceAccessDetails;
|
||||
import ca.uhn.fhir.rest.api.server.SimplePreResourceShowDetails;
|
||||
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
|
@ -48,7 +52,13 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
@ -119,8 +129,8 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
|||
}
|
||||
|
||||
@Create
|
||||
public MethodOutcome create(@ResourceParam T theResource) {
|
||||
createInternal(theResource);
|
||||
public MethodOutcome create(@ResourceParam T theResource, RequestDetails theRequestDetails) {
|
||||
createInternal(theResource, theRequestDetails);
|
||||
|
||||
myCreateCount.incrementAndGet();
|
||||
|
||||
|
@ -130,17 +140,17 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
|||
.setId(theResource.getIdElement());
|
||||
}
|
||||
|
||||
private void createInternal(@ResourceParam T theResource) {
|
||||
private void createInternal(@ResourceParam T theResource, RequestDetails theRequestDetails) {
|
||||
long idPart = myNextId++;
|
||||
String idPartAsString = Long.toString(idPart);
|
||||
Long versionIdPart = 1L;
|
||||
|
||||
IIdType id = store(theResource, idPartAsString, versionIdPart);
|
||||
IIdType id = store(theResource, idPartAsString, versionIdPart, theRequestDetails);
|
||||
theResource.setId(id);
|
||||
}
|
||||
|
||||
@Delete
|
||||
public MethodOutcome delete(@IdParam IIdType theId) {
|
||||
public MethodOutcome delete(@IdParam IIdType theId, RequestDetails theRequestDetails) {
|
||||
TreeMap<Long, T> versions = myIdToVersionToResourceMap.get(theId.getIdPart());
|
||||
if (versions == null || versions.isEmpty()) {
|
||||
throw new ResourceNotFoundException(theId);
|
||||
|
@ -148,7 +158,7 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
|||
|
||||
|
||||
long nextVersion = versions.lastEntry().getKey() + 1L;
|
||||
IIdType id = store(null, theId.getIdPart(), nextVersion);
|
||||
IIdType id = store(null, theId.getIdPart(), nextVersion, theRequestDetails);
|
||||
|
||||
myDeleteCount.incrementAndGet();
|
||||
|
||||
|
@ -310,7 +320,7 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
|||
return fireInterceptorsAndFilterAsNeeded(retVal, theRequestDetails);
|
||||
}
|
||||
|
||||
private IIdType store(@ResourceParam T theResource, String theIdPart, Long theVersionIdPart) {
|
||||
private IIdType store(@ResourceParam T theResource, String theIdPart, Long theVersionIdPart, RequestDetails theRequestDetails) {
|
||||
IIdType id = myFhirContext.getVersion().newIdType();
|
||||
String versionIdPart = Long.toString(theVersionIdPart);
|
||||
id.setParts(null, myResourceName, theIdPart, versionIdPart);
|
||||
|
@ -348,6 +358,35 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
|||
TreeMap<Long, T> versionToResource = getVersionToResource(theIdPart);
|
||||
versionToResource.put(theVersionIdPart, theResource);
|
||||
|
||||
if (theRequestDetails != null) {
|
||||
IInterceptorBroadcaster interceptorBroadcaster = theRequestDetails.getInterceptorBroadcaster();
|
||||
|
||||
if (theResource != null) {
|
||||
if (!myIdToHistory.containsKey(theIdPart)) {
|
||||
|
||||
// Interceptor call: STORAGE_PRESTORAGE_RESOURCE_CREATED
|
||||
HookParams params = new HookParams()
|
||||
.add(RequestDetails.class, theRequestDetails)
|
||||
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
|
||||
.add(IBaseResource.class, theResource);
|
||||
interceptorBroadcaster.callHooks(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED, params);
|
||||
interceptorBroadcaster.callHooks(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED, params);
|
||||
|
||||
} else {
|
||||
|
||||
// Interceptor call: STORAGE_PRESTORAGE_RESOURCE_UPDATED
|
||||
HookParams params = new HookParams()
|
||||
.add(RequestDetails.class, theRequestDetails)
|
||||
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
|
||||
.add(IBaseResource.class, myIdToHistory.get(theIdPart).getFirst())
|
||||
.add(IBaseResource.class, theResource);
|
||||
interceptorBroadcaster.callHooks(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, params);
|
||||
interceptorBroadcaster.callHooks(Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED, params);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store to type history map
|
||||
myTypeHistory.addFirst(theResource);
|
||||
|
||||
|
@ -365,11 +404,12 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
|||
@Update
|
||||
public MethodOutcome update(
|
||||
@ResourceParam T theResource,
|
||||
@ConditionalUrlParam String theConditional) {
|
||||
@ConditionalUrlParam String theConditional,
|
||||
RequestDetails theRequestDetails) {
|
||||
|
||||
ValidateUtil.isTrueOrThrowInvalidRequest(isBlank(theConditional), "This server doesn't support conditional update");
|
||||
|
||||
boolean created = updateInternal(theResource);
|
||||
boolean created = updateInternal(theResource, theRequestDetails);
|
||||
myUpdateCount.incrementAndGet();
|
||||
|
||||
return new MethodOutcome()
|
||||
|
@ -378,7 +418,7 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
|||
.setId(theResource.getIdElement());
|
||||
}
|
||||
|
||||
private boolean updateInternal(@ResourceParam T theResource) {
|
||||
private boolean updateInternal(@ResourceParam T theResource, RequestDetails theRequestDetails) {
|
||||
String idPartAsString = theResource.getIdElement().getIdPart();
|
||||
TreeMap<Long, T> versionToResource = getVersionToResource(idPartAsString);
|
||||
|
||||
|
@ -392,7 +432,7 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
|||
created = false;
|
||||
}
|
||||
|
||||
IIdType id = store(theResource, idPartAsString, versionIdPart);
|
||||
IIdType id = store(theResource, idPartAsString, versionIdPart, theRequestDetails);
|
||||
theResource.setId(id);
|
||||
return created;
|
||||
}
|
||||
|
@ -411,9 +451,9 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
|||
*/
|
||||
public IIdType store(T theResource) {
|
||||
if (theResource.getIdElement().hasIdPart()) {
|
||||
updateInternal(theResource);
|
||||
updateInternal(theResource, null);
|
||||
} else {
|
||||
createInternal(theResource);
|
||||
createInternal(theResource, null);
|
||||
}
|
||||
return theResource.getIdElement();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package ca.uhn.fhir.rest.server.provider;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||
import ca.uhn.fhir.rest.gclient.IDeleteTyped;
|
||||
|
@ -8,6 +10,7 @@ import ca.uhn.fhir.rest.server.IResourceProvider;
|
|||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
|
@ -21,6 +24,10 @@ import org.junit.AfterClass;
|
|||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -28,11 +35,17 @@ import javax.servlet.ServletException;
|
|||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.matchesPattern;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class HashMapResourceProviderTest {
|
||||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(HashMapResourceProviderTest.class);
|
||||
|
@ -43,6 +56,9 @@ public class HashMapResourceProviderTest {
|
|||
private static HashMapResourceProvider<Patient> myPatientResourceProvider;
|
||||
private static HashMapResourceProvider<Observation> myObservationResourceProvider;
|
||||
|
||||
@Mock
|
||||
private IAnonymousInterceptor myAnonymousInterceptor;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
ourRestServer.clearData();
|
||||
|
@ -52,6 +68,9 @@ public class HashMapResourceProviderTest {
|
|||
|
||||
@Test
|
||||
public void testCreateAndRead() {
|
||||
ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED, myAnonymousInterceptor);
|
||||
ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED, myAnonymousInterceptor);
|
||||
|
||||
// Create
|
||||
Patient p = new Patient();
|
||||
p.setActive(true);
|
||||
|
@ -59,6 +78,9 @@ public class HashMapResourceProviderTest {
|
|||
assertThat(id.getIdPart(), matchesPattern("[0-9]+"));
|
||||
assertEquals("1", id.getVersionIdPart());
|
||||
|
||||
verify(myAnonymousInterceptor, Mockito.times(1)).invoke(eq(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED), any());
|
||||
verify(myAnonymousInterceptor, Mockito.times(1)).invoke(eq(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED), any());
|
||||
|
||||
// Read
|
||||
p = (Patient) ourClient.read().resource("Patient").withId(id).execute();
|
||||
assertEquals(true, p.getActive());
|
||||
|
@ -282,6 +304,9 @@ public class HashMapResourceProviderTest {
|
|||
assertEquals("1", id.getVersionIdPart());
|
||||
|
||||
// Update
|
||||
ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, myAnonymousInterceptor);
|
||||
ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED, myAnonymousInterceptor);
|
||||
|
||||
p = new Patient();
|
||||
p.setId(id);
|
||||
p.setActive(false);
|
||||
|
@ -289,6 +314,9 @@ public class HashMapResourceProviderTest {
|
|||
assertThat(id.getIdPart(), matchesPattern("[0-9]+"));
|
||||
assertEquals("2", id.getVersionIdPart());
|
||||
|
||||
verify(myAnonymousInterceptor, Mockito.times(1)).invoke(eq(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED), any());
|
||||
verify(myAnonymousInterceptor, Mockito.times(1)).invoke(eq(Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED), any());
|
||||
|
||||
assertEquals(1, myPatientResourceProvider.getCountCreate());
|
||||
assertEquals(1, myPatientResourceProvider.getCountUpdate());
|
||||
|
||||
|
@ -305,33 +333,6 @@ public class HashMapResourceProviderTest {
|
|||
}
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
JettyUtil.closeServer(ourListenerServer);
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void startListenerServer() throws Exception {
|
||||
ourRestServer = new MyRestfulServer();
|
||||
|
||||
ourListenerServer = new Server(0);
|
||||
|
||||
ServletContextHandler proxyHandler = new ServletContextHandler();
|
||||
proxyHandler.setContextPath("/");
|
||||
|
||||
ServletHolder servletHolder = new ServletHolder();
|
||||
servletHolder.setServlet(ourRestServer);
|
||||
proxyHandler.addServlet(servletHolder, "/*");
|
||||
|
||||
ourListenerServer.setHandler(proxyHandler);
|
||||
JettyUtil.startServer(ourListenerServer);
|
||||
int ourListenerPort = JettyUtil.getPortForStartedServer(ourListenerServer);
|
||||
String ourBase = "http://localhost:" + ourListenerPort + "/";
|
||||
ourCtx.getRestfulClientFactory().setSocketTimeout(120000);
|
||||
ourClient = ourCtx.newRestfulGenericClient(ourBase);
|
||||
}
|
||||
|
||||
private static class MyRestfulServer extends RestfulServer {
|
||||
|
||||
MyRestfulServer() {
|
||||
|
@ -359,5 +360,32 @@ public class HashMapResourceProviderTest {
|
|||
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
JettyUtil.closeServer(ourListenerServer);
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void startListenerServer() throws Exception {
|
||||
ourRestServer = new MyRestfulServer();
|
||||
|
||||
ourListenerServer = new Server(0);
|
||||
|
||||
ServletContextHandler proxyHandler = new ServletContextHandler();
|
||||
proxyHandler.setContextPath("/");
|
||||
|
||||
ServletHolder servletHolder = new ServletHolder();
|
||||
servletHolder.setServlet(ourRestServer);
|
||||
proxyHandler.addServlet(servletHolder, "/*");
|
||||
|
||||
ourListenerServer.setHandler(proxyHandler);
|
||||
JettyUtil.startServer(ourListenerServer);
|
||||
int ourListenerPort = JettyUtil.getPortForStartedServer(ourListenerServer);
|
||||
String ourBase = "http://localhost:" + ourListenerPort + "/";
|
||||
ourCtx.getRestfulClientFactory().setSocketTimeout(120000);
|
||||
ourClient = ourCtx.newRestfulGenericClient(ourBase);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor;
|
|||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -47,6 +48,7 @@ public class PointcutLatch implements IAnonymousInterceptor, IPointcutLatch {
|
|||
private final String myName;
|
||||
private final AtomicLong myLastInvoke = new AtomicLong();
|
||||
private final AtomicReference<CountDownLatch> myCountdownLatch = new AtomicReference<>();
|
||||
private final AtomicReference<String> myCountdownLatchSetStacktrace = new AtomicReference<>();
|
||||
private final AtomicReference<List<String>> myFailures = new AtomicReference<>();
|
||||
private final AtomicReference<List<HookParams>> myCalledWith = new AtomicReference<>();
|
||||
private final Pointcut myPointcut;
|
||||
|
@ -80,7 +82,8 @@ public class PointcutLatch implements IAnonymousInterceptor, IPointcutLatch {
|
|||
|
||||
public void setExpectedCount(int theCount, boolean theExactMatch) {
|
||||
if (myCountdownLatch.get() != null) {
|
||||
throw new PointcutLatchException("setExpectedCount() called before previous awaitExpected() completed.");
|
||||
String previousStack = myCountdownLatchSetStacktrace.get();
|
||||
throw new PointcutLatchException("setExpectedCount() called before previous awaitExpected() completed. Previous set stack:\n" + previousStack);
|
||||
}
|
||||
myExactMatch = theExactMatch;
|
||||
createLatch(theCount);
|
||||
|
@ -99,6 +102,11 @@ public class PointcutLatch implements IAnonymousInterceptor, IPointcutLatch {
|
|||
myFailures.set(Collections.synchronizedList(new ArrayList<>()));
|
||||
myCalledWith.set(Collections.synchronizedList(new ArrayList<>()));
|
||||
myCountdownLatch.set(new CountDownLatch(theCount));
|
||||
try {
|
||||
throw new Exception();
|
||||
} catch (Exception e) {
|
||||
myCountdownLatchSetStacktrace.set(ExceptionUtils.getStackTrace(e));
|
||||
}
|
||||
myInitialCount = theCount;
|
||||
}
|
||||
|
||||
|
@ -151,6 +159,7 @@ public class PointcutLatch implements IAnonymousInterceptor, IPointcutLatch {
|
|||
@Override
|
||||
public void clear() {
|
||||
myCountdownLatch.set(null);
|
||||
myCountdownLatchSetStacktrace.set(null);
|
||||
}
|
||||
|
||||
private String toCalledWithString() {
|
||||
|
|
Loading…
Reference in New Issue