View Javadoc
1   package ca.uhn.fhir.jpa.subscription.resthook;
2   
3   /*-
4    * #%L
5    * HAPI FHIR JPA Server
6    * %%
7    * Copyright (C) 2014 - 2018 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  
23  import ca.uhn.fhir.context.FhirContext;
24  import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
25  import ca.uhn.fhir.jpa.subscription.*;
26  import ca.uhn.fhir.rest.api.EncodingEnum;
27  import ca.uhn.fhir.rest.api.RequestTypeEnum;
28  import ca.uhn.fhir.rest.client.api.*;
29  import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor;
30  import ca.uhn.fhir.rest.gclient.IClientExecutable;
31  import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
32  import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
33  import org.hl7.fhir.instance.model.api.IBaseResource;
34  import org.hl7.fhir.instance.model.api.IIdType;
35  import org.hl7.fhir.r4.model.Subscription;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  import org.springframework.messaging.MessagingException;
39  
40  import java.io.IOException;
41  import java.util.ArrayList;
42  import java.util.HashMap;
43  import java.util.List;
44  import java.util.Map;
45  
46  import static org.apache.commons.lang3.StringUtils.isNotBlank;
47  
48  public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDeliverySubscriber {
49  	private Logger ourLog = LoggerFactory.getLogger(SubscriptionDeliveringRestHookSubscriber.class);
50  
51  	public SubscriptionDeliveringRestHookSubscriber(IFhirResourceDao<?> theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
52  		super(theSubscriptionDao, theChannelType, theSubscriptionInterceptor);
53  	}
54  
55  	protected void deliverPayload(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription, EncodingEnum thePayloadType, IGenericClient theClient) {
56  		IBaseResource payloadResource = getAndMassagePayload(theMsg, theSubscription);
57  		if (payloadResource == null) return;
58  
59  		doDelivery(theMsg, theSubscription, thePayloadType, theClient, payloadResource);
60  	}
61  
62  	protected void doDelivery(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription, EncodingEnum thePayloadType, IGenericClient theClient, IBaseResource thePayloadResource) {
63  		IClientExecutable<?, ?> operation;
64  		switch (theMsg.getOperationType()) {
65  			case CREATE:
66  				if (thePayloadResource == null || thePayloadResource.isEmpty()) {
67  					if (thePayloadType != null ) {
68  						operation = theClient.create().resource(thePayloadResource);
69  					} else {
70  						sendNotification(theMsg);
71  						return;
72  					}
73  				} else {
74  					if (thePayloadType != null ) {
75  						operation = theClient.update().resource(thePayloadResource);
76  					} else {
77  						sendNotification(theMsg);
78  						return;
79  					}
80  				}
81  				break;
82  			case UPDATE:
83  				if (thePayloadResource == null || thePayloadResource.isEmpty()) {
84  					if (thePayloadType != null ) {
85  						operation = theClient.create().resource(thePayloadResource);
86  					} else {
87  						sendNotification(theMsg);
88  						return;
89  					}
90  				} else {
91  					if (thePayloadType != null ) {
92  						operation = theClient.update().resource(thePayloadResource);
93  					} else {
94  						sendNotification(theMsg);
95  						return;
96  					}
97  				}
98  				break;
99  			case DELETE:
100 				operation = theClient.delete().resourceById(theMsg.getPayloadId(getContext()));
101 				break;
102 			default:
103 				ourLog.warn("Ignoring delivery message of type: {}", theMsg.getOperationType());
104 				return;
105 		}
106 
107 		if (thePayloadType != null) {
108 			operation.encoded(thePayloadType);
109 		}
110 
111 		ourLog.info("Delivering {} rest-hook payload {} for {}", theMsg.getOperationType(), thePayloadResource.getIdElement().toUnqualified().getValue(), theSubscription.getIdElement(getContext()).toUnqualifiedVersionless().getValue());
112 
113 		try {
114 			operation.execute();
115 		} catch (ResourceNotFoundException e) {
116 			ourLog.error("Cannot reach "+ theMsg.getSubscription().getEndpointUrl());
117 			e.printStackTrace();
118 			throw e;
119 		}
120 	}
121 
122 	protected IBaseResource getAndMassagePayload(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription) {
123 		IBaseResource payloadResource = theMsg.getPayload(getContext());
124 
125 		if (theSubscription.getRestHookDetails().isDeliverLatestVersion()) {
126 			IFhirResourceDao dao = getSubscriptionInterceptor().getDao(payloadResource.getClass());
127 			try {
128 				payloadResource = dao.read(payloadResource.getIdElement().toVersionless());
129 			} catch (ResourceGoneException e) {
130 				ourLog.warn("Resource {} is deleted, not going to deliver for subscription {}", payloadResource.getIdElement(), theSubscription.getIdElement(getContext()));
131 				return null;
132 			}
133 		}
134 
135 		IIdType resourceId = payloadResource.getIdElement();
136 		if (theSubscription.getRestHookDetails().isStripVersionId()) {
137 			resourceId = resourceId.toVersionless();
138 			payloadResource.setId(resourceId);
139 		}
140 		return payloadResource;
141 	}
142 
143 	@Override
144 	public void handleMessage(ResourceDeliveryMessage theMessage) throws MessagingException {
145 			CanonicalSubscription subscription = theMessage.getSubscription();
146 
147 			// Grab the endpoint from the subscription
148 			String endpointUrl = subscription.getEndpointUrl();
149 
150 			// Grab the payload type (encoding mimetype) from the subscription
151 			String payloadString = subscription.getPayloadString();
152 			EncodingEnum payloadType = null;
153 			if(payloadString != null) {
154 				if (payloadString.contains(";")) {
155 					payloadString = payloadString.substring(0, payloadString.indexOf(';'));
156 				}
157 				payloadString = payloadString.trim();
158 				payloadType = EncodingEnum.forContentType(payloadString);
159 			}
160 
161 			// Create the client request
162 			getContext().getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
163 			IGenericClient client = null;
164 			if (isNotBlank(endpointUrl)) {
165 				client = getContext().newRestfulGenericClient(endpointUrl);
166 
167 				// Additional headers specified in the subscription
168 				List<String> headers = subscription.getHeaders();
169 				for (String next : headers) {
170 					if (isNotBlank(next)) {
171 						client.registerInterceptor(new SimpleRequestHeaderInterceptor(next));
172 					}
173 				}
174 			}
175 
176 			deliverPayload(theMessage, subscription, payloadType, client);
177 	}
178 
179 	/**
180 	 * Sends a POST notification without a payload
181 	 * @param theMsg
182 	 */
183 	protected void sendNotification(ResourceDeliveryMessage theMsg) {
184 		FhirContext context= getContext();
185 		Map<String, List<String>> params = new HashMap();
186 		List<Header> headers = new ArrayList<>();
187 		StringBuilder url = new StringBuilder(theMsg.getSubscription().getEndpointUrl());
188 		IHttpClient client = context.getRestfulClientFactory().getHttpClient(url, params, "", RequestTypeEnum.POST, headers);
189 		IHttpRequest request = client.createParamRequest(context, params, null);
190 		try {
191 			IHttpResponse response = request.execute();
192 		} catch (IOException e) {
193 			ourLog.error("Error trying to reach "+ theMsg.getSubscription().getEndpointUrl());
194 			e.printStackTrace();
195 			throw new ResourceNotFoundException(e.getMessage());
196 		}
197 	}
198 }