Added ability to create a resthook subscription without returning a payload using an HTTP POST for notification

This commit is contained in:
Jeff Chung 2017-12-22 17:14:45 -08:00
parent 502f58022e
commit eb2d21ac4b
5 changed files with 98 additions and 29 deletions

View File

@ -2105,14 +2105,14 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override
public IUpdateTyped resource(IBaseResource theResource) {
Validate.notNull(theResource, "Resource can not be null");
//Validate.notNull(theResource, "Resource can not be null");
myResource = theResource;
return this;
}
@Override
public IUpdateTyped resource(String theResourceBody) {
Validate.notBlank(theResourceBody, "Body can not be null or blank");
//Validate.notBlank(theResourceBody, "Body can not be null or blank");
myResourceBody = theResourceBody;
return this;
}

View File

@ -98,11 +98,7 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
}
protected void validateChannelPayload(Subscription theResource) {
if (isBlank(theResource.getChannel().getPayload())) {
throw new UnprocessableEntityException("Subscription.channel.payload must be populated for rest-hook subscriptions");
}
if (EncodingEnum.forContentType(theResource.getChannel().getPayload()) == null) {
if (!isBlank(theResource.getChannel().getPayload()) && EncodingEnum.forContentType(theResource.getChannel().getPayload()) == null) {
throw new UnprocessableEntityException("Invalid value for Subscription.channel.payload: " + theResource.getChannel().getPayload());
}
}

View File

@ -99,11 +99,7 @@ public class FhirResourceDaoSubscriptionR4 extends FhirResourceDaoR4<Subscriptio
}
protected void validateChannelPayload(Subscription theResource) {
if (isBlank(theResource.getChannel().getPayload())) {
throw new UnprocessableEntityException("Subscription.channel.payload must be populated for rest-hook subscriptions");
}
if (EncodingEnum.forContentType(theResource.getChannel().getPayload()) == null) {
if (!isBlank(theResource.getChannel().getPayload()) && EncodingEnum.forContentType(theResource.getChannel().getPayload()) == null) {
throw new UnprocessableEntityException("Invalid value for Subscription.channel.payload: " + theResource.getChannel().getPayload());
}
}

View File

@ -20,24 +20,26 @@ package ca.uhn.fhir.jpa.subscription.resthook;
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.subscription.*;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.client.api.*;
import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor;
import ca.uhn.fhir.rest.gclient.IClientExecutable;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -54,10 +56,38 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe
IClientExecutable<?, ?> operation;
switch (theMsg.getOperationType()) {
case CREATE:
if (payloadResource == null || payloadResource.isEmpty()) {
if (thePayloadType != null ) {
operation = theClient.create().resource(payloadResource);
} else {
sendNotification(theMsg);
return;
}
} else {
if (thePayloadType != null ) {
operation = theClient.update().resource(payloadResource);
} else {
sendNotification(theMsg);
return;
}
}
break;
case UPDATE:
if (payloadResource == null || payloadResource.isEmpty()) {
if (thePayloadType != null ) {
operation = theClient.create().resource(payloadResource);
} else {
sendNotification(theMsg);
return;
}
} else {
if (thePayloadType != null ) {
operation = theClient.update().resource(payloadResource);
} else {
sendNotification(theMsg);
return;
}
}
break;
case DELETE:
operation = theClient.delete().resourceById(theMsg.getPayloadId(getContext()));
@ -67,11 +97,19 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe
return;
}
if (thePayloadType != null) {
operation.encoded(thePayloadType);
}
ourLog.info("Delivering {} rest-hook payload {} for {}", theMsg.getOperationType(), payloadResource.getIdElement().toUnqualified().getValue(), theSubscription.getIdElement(getContext()).toUnqualifiedVersionless().getValue());
try {
operation.execute();
} catch (ResourceNotFoundException e) {
ourLog.error("Cannot reach "+ theMsg.getSubscription().getEndpointUrl());
e.printStackTrace();
throw e;
}
}
@Override
@ -83,13 +121,14 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe
// Grab the payload type (encoding mimetype) from the subscription
String payloadString = subscription.getPayloadString();
payloadString = StringUtils.defaultString(payloadString, Constants.CT_FHIR_XML_NEW);
EncodingEnum payloadType = null;
if(payloadString != null) {
if (payloadString.contains(";")) {
payloadString = payloadString.substring(0, payloadString.indexOf(';'));
}
payloadString = payloadString.trim();
EncodingEnum payloadType = EncodingEnum.forContentType(payloadString);
payloadType = ObjectUtils.defaultIfNull(payloadType, EncodingEnum.XML);
payloadType = EncodingEnum.forContentType(payloadString);
}
// Create the client request
getContext().getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
@ -109,4 +148,23 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe
deliverPayload(theMessage, subscription, payloadType, client);
}
/**
* Sends a POST notification without a payload
* @param theMsg
*/
protected void sendNotification(ResourceDeliveryMessage theMsg) {
FhirContext context= getContext();
Map<String, List<String>> params = new HashMap();
List<Header> headers = new ArrayList<>();
StringBuilder url = new StringBuilder(theMsg.getSubscription().getEndpointUrl());
IHttpClient client = context.getRestfulClientFactory().getHttpClient(url, params, "", RequestTypeEnum.POST, headers);
IHttpRequest request = client.createParamRequest(context, params, null);
try {
IHttpResponse response = request.execute();
} catch (IOException e) {
ourLog.error("Error trying to reach "+ theMsg.getSubscription().getEndpointUrl());
e.printStackTrace();
throw new ResourceNotFoundException(e.getMessage());
}
}
}

View File

@ -34,7 +34,7 @@ import static org.junit.Assert.fail;
*/
public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestDstu2Test.class);
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestDstu3Test.class);
private static List<Observation> ourCreatedObservations = Lists.newArrayList();
private static int ourListenerPort;
private static RestfulServer ourListenerRestServer;
@ -311,6 +311,25 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
assertEquals(Constants.CT_FHIR_XML_NEW, ourContentTypes.get(0));
}
@Test
public void testRestHookSubscriptionWithoutPayload() throws Exception {
String payload = "";
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code;
String criteria2 = "Observation?code=SNOMED-CT|" + code + "111";
Subscription subscription1 = createSubscription(criteria1, payload, ourListenerServerBase);
Subscription subscription2 = createSubscription(criteria2, payload, ourListenerServerBase);
Observation observation1 = sendObservation(code, "SNOMED-CT");
// Should see 1 subscription notification, but no payload
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
waitForSize(0, ourUpdatedObservations);
}
// TODO: Reenable this
@Test
@Ignore