001package ca.cdr.api.fhir.interceptor;
002
003import ca.cdr.api.fhirgw.json.GatewayTargetJson;
004import ca.cdr.api.fhirgw.model.OperationRequest;
005import ca.cdr.api.fhirgw.model.OperationResponse;
006import ca.cdr.api.fhirgw.model.ReadRequest;
007import ca.cdr.api.fhirgw.model.SearchPageRequest;
008import ca.cdr.api.fhirgw.model.SearchRequest;
009import ca.cdr.api.fhirgw.model.SearchResponse;
010import ca.cdr.api.fhirgw.model.SearchResultsAccumulator;
011import ca.uhn.fhir.interceptor.api.IPointcut;
012import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
013import org.apache.commons.lang3.ArrayUtils;
014
015import javax.annotation.Nonnull;
016import java.security.KeyStore;
017import java.util.Arrays;
018import java.util.Collections;
019import java.util.HashSet;
020import java.util.List;
021import java.util.Set;
022import java.util.stream.Collectors;
023
024/**
025 * Value for {@link CdrHook#value()}
026 * <p>
027 * Hook pointcuts are divided into several broad categories:
028 * <ul>
029 * <li>FHIRGW_xxx: Hooks on the FHIR Gateway module</li>
030 * </ul>
031 * </p>
032 */
033public enum CdrPointcut implements IPointcut {
034
035        /**
036         * <b>FHIR Gateway Hook:</b>
037         * This hook is called when the FHIR Gateway is about to invoke a FHIR <b>read</b> or <b>vread</b> operation against an individual
038         * target server. This hook is called once for each target that will be called, so if a single client read is being
039         * multicasted against two target servers, this hook will be invoked twice.
040         * <p>
041         * Hooks may accept the following parameters:
042         * <ul>
043         * <li>
044         * {@link ca.cdr.api.fhirgw.model.ReadRequest} - The read that is about to be invoked. The hook method can modify this request, and modifications will affect the operation that is actually performed against the target server.
045         * </li>
046         * <li>
047         * {@link ca.cdr.api.fhirgw.json.GatewayTargetJson} - The gateway target server definition. Hook methods should not modify this object, and any changes will be ignored.
048         * </li>
049         * <li>
050         * {@link ca.uhn.fhir.rest.server.servlet.ServletRequestDetails} - A bean containing details about the request that
051         * is about to be processed.
052         * </li>
053         * </ul>
054         * </p>
055         * Hook methods must return <code>void</code>.
056         */
057        FHIRGW_READ_TARGET_PREINVOKE(void.class,
058                ReadRequest.class,
059                GatewayTargetJson.class,
060                ServletRequestDetails.class
061        ),
062
063        /**
064         * <b>FHIR Gateway Hook:</b>
065         * This hook is called when the FHIR Gateway is about to invoke a FHIR <b>operation</b> operation against an individual
066         * target server.
067         * <p>
068         * Hooks may accept the following parameters:
069         * <ul>
070         * <li>
071         * {@link ca.cdr.api.fhirgw.model.OperationRequest} - The read that is about to be invoked. The hook method can modify this request, and modifications will affect the operation that is actually performed against the target server.
072         * </li>
073         * <li>
074         * {@link ca.cdr.api.fhirgw.json.GatewayTargetJson} - The gateway target server definition. Hook methods should not modify this object, and any changes will be ignored.
075         * </li>
076         * <li>
077         * {@link ca.uhn.fhir.rest.server.servlet.ServletRequestDetails} - A bean containing details about the request that
078         * is about to be processed.
079         * </li>
080         * </ul>
081         * </p>
082         * Hook methods must return <code>void</code>.
083         */
084        FHIRGW_OPERATION_TARGET_PREINVOKE(void.class,
085                OperationRequest.class,
086                GatewayTargetJson.class,
087                ServletRequestDetails.class
088        ),
089
090
091        /**
092         * <b>FHIR Gateway Hook:</b>
093         * This hook is called when the FHIR Gateway has finished invoking a FHIR <b>extended operation</b> operation against an individual
094         * target server. This hook is called once for each target that has been called, so if a single client operation is being
095         * multicasted against two target servers, this hook will be invoked twice.
096         * <p>
097         * <p>
098         * Hooks may accept the following parameters:
099         * <ul>
100         * <li>
101         * {@link ca.cdr.api.fhirgw.json.GatewayTargetJson} - The gateway target server definition. Hook methods should not modify this object, and any changes will be ignored.
102         * </li>
103         * <li>
104         * {@link ca.cdr.api.fhirgw.model.SearchResultsAccumulator} - The accumulator being used to collect the search results so far. This may be empty in the case of operations
105         * which do not return search results. Some operations, such as <b>$everything</b>, will return search results, but others such as <b>$</b>
106         * Hook methods may use this object to inspect results received by other endpoints when searching in serial mode, and can
107         * modify the results as needed. Note that the {@link #FHIRGW_SEARCH_TARGET_POSTINVOKE} pointcut is invoked once for each gateway
108         * target, <b>before</b> the search results are added to the accumulator. Results from the current target are found in the
109         * {@link SearchResponse} object, and will be moved from that object into the accumulator after this pointcut is complete.
110         * </li>
111         * <li>
112         * {@link ca.cdr.api.fhirgw.model.OperationResponse} - This object contains the Operation Response from the individual Gateway Target that was called. Interceptors may modify this object in any way they want. This may be null if the operation returns a Bundle (check the SearchResultsAccumulator instead).
113         * </li>
114         * </ul>
115         * </p>
116         * Hook methods must return <code>void</code>.
117         *
118         **/
119        FHIRGW_OPERATION_TARGET_POSTINVOKE(void.class,
120                OperationRequest.class,
121                SearchResultsAccumulator.class,
122                OperationResponse.class,
123                GatewayTargetJson.class,
124                ServletRequestDetails.class
125        ),
126
127        /**
128         * <b>FHIR Gateway Hook:</b>
129         * This hook is called when the FHIR Gateway is about to invoke a FHIR <b>search</b> operation against an individual
130         * target server. This hook is called once for each target that will be called, so if a single client search is being
131         * multicasted against two target servers, this hook will be invoked twice.
132         * <p>
133         * This hook can be contrasted with {@link #FHIRGW_SEARCH_PAGE_TARGET_PREINVOKE}:
134         * <ul>
135         *    <li>{@link #FHIRGW_SEARCH_TARGET_PREINVOKE} is called before the initial search is performed (which should return search results as well as paging links)</li>
136         *    <li>{@link #FHIRGW_SEARCH_PAGE_TARGET_PREINVOKE} is called before the subsequent pages of results are fetched</li>
137         * </ul>
138         * </p>
139         * <p>
140         * Hooks may accept the following parameters:
141         * <ul>
142         * <li>
143         * {@link ca.cdr.api.fhirgw.model.SearchRequest} - The search that is about to be invoked. The hook method can modify this request, and modifications will affect the operation that is actually performed against the target server.
144         * </li>
145         * <li>
146         * {@link ca.cdr.api.fhirgw.json.GatewayTargetJson} - The gateway target server definition. Hook methods should not modify this object, and any changes will be ignored.
147         * </li>
148         * <li>
149         * {@link ca.cdr.api.fhirgw.model.SearchResultsAccumulator} - The accumulator being used to collect the search results so far. Hook methods may use this object to inspect results recieved by other endpoints when searching in serial mode, and can modify the results as needed.
150         * </li>
151         * <li>
152         * {@link ca.uhn.fhir.rest.server.servlet.ServletRequestDetails} - A bean containing details about the request that
153         * is about to be processed.
154         * </li>
155         * </ul>
156         * </p>
157         * Hook methods must return <code>void</code>.
158         */
159        FHIRGW_SEARCH_TARGET_PREINVOKE(void.class,
160                SearchRequest.class,
161                GatewayTargetJson.class,
162                SearchResultsAccumulator.class,
163                ServletRequestDetails.class
164                ),
165
166        /**
167         * <b>FHIR Gateway Hook:</b>
168         * This hook is called when the FHIR Gateway is about to invoke a FHIR <b>search</b> paging operation against an individual
169         * target server. This hook is called once for each target that will be called, so if a single client search is being
170         * multicasted against two target servers, this hook will be invoked twice.
171         * <p>
172         * This hook can be contrasted with {@link #FHIRGW_SEARCH_TARGET_PREINVOKE}:
173         * <ul>
174         *    <li>{@link #FHIRGW_SEARCH_TARGET_PREINVOKE} is called before the initial search is performed (which should return search results as well as paging links)</li>
175         *    <li>{@link #FHIRGW_SEARCH_PAGE_TARGET_PREINVOKE} is called before the subsequent pages of results are fetched</li>
176         * </ul>
177         * </p>
178         * <p>
179         * Hooks may accept the following parameters:
180         * <ul>
181         * <li>
182         * {@link ca.cdr.api.fhirgw.model.SearchPageRequest} - The search that is about to be invoked. The hook method can modify this request, and modifications will affect the operation that is actually performed against the target server.
183         * </li>
184         * <li>
185         * {@link ca.cdr.api.fhirgw.json.GatewayTargetJson} - The gateway target server definition. Hook methods should not modify this object, and any changes will be ignored.
186         * </li>
187         * <li>
188         * {@link ca.cdr.api.fhirgw.model.SearchResultsAccumulator} - The accumulator being used to collect the search results so far. Hook methods may use this object to inspect results received by other endpoints when searching in serial mode, and can modify the results as needed.
189         * </li>
190         * <li>
191         * {@link ca.uhn.fhir.rest.server.servlet.ServletRequestDetails} - A bean containing details about the request that
192         * is about to be processed.
193         * </li>
194         * </ul>
195         * </p>
196         * Hook methods must return <code>void</code>.
197         */
198        FHIRGW_SEARCH_PAGE_TARGET_PREINVOKE(void.class,
199                SearchPageRequest.class,
200                GatewayTargetJson.class,
201                SearchResultsAccumulator.class,
202                ServletRequestDetails.class
203        ),
204
205        /**
206         * <b>FHIR Gateway Hook:</b>
207         * This hook is called when the FHIR Gateway has finished invoking a FHIR <b>search</b> operation against an individual
208         * target server. This hook is called once for each target that has been called, so if a single client search is being
209         * multicasted against two target servers, this hook will be invoked twice.
210         * <p>
211         * <p>
212         * Hooks may accept the following parameters:
213         * <ul>
214         * <li>
215         * {@link ca.cdr.api.fhirgw.json.GatewayTargetJson} - The gateway target server definition. Hook methods should not modify this object, and any changes will be ignored.
216         * </li>
217         * <li>
218         * {@link ca.cdr.api.fhirgw.model.SearchResultsAccumulator} - The accumulator being used to collect the search results so far.
219         * Hook methods may use this object to inspect results received by other endpoints when searching in serial mode, and can
220         * modify the results as needed. Note that the {@link #FHIRGW_SEARCH_TARGET_POSTINVOKE} pointcut is invoked once for each gateway
221         * target, <b>before</b> the search results are added to the accumulator. Results from the current target are found in the
222         * {@link SearchResponse} object, and will be moved from that object into the accumulator after this pointcut is complete.
223         * </li>
224         * <li>
225         * {@link ca.cdr.api.fhirgw.model.SearchResponse} - This object contains the search results from the individual Gateway Target that was called. Interceptors may modify this object in any way they want.
226         * </li>
227         * </ul>
228         * </p>
229         * Hook methods must return <code>void</code>.
230         */
231        FHIRGW_SEARCH_TARGET_POSTINVOKE(void.class,
232                GatewayTargetJson.class,
233                SearchResultsAccumulator.class,
234                SearchResponse.class,
235                ServletRequestDetails.class
236        ),
237
238        /**
239         * <b>Endpoint Hook:</b>
240         * The pointcut provides the capability to supply a provisioned KeyStore file for TLS base encryption.
241         * Note that pointcut {@link #SERVER_CONFIGURATION_KEYSTORE} is invoked only if the endpoint listener
242         * is said to required TLS encryption for incoming connections through environment property <b>tls.enabled</b>
243         * <p>
244         * <p>
245         * Hooks may accept the following parameters:
246         * <ul>
247         * <li>
248         * {@java.lang.String} - The keystore password
249         * </li>
250         * </ul>
251         *
252         * </p>
253         * Hook methods must return <code>KeyStore</code>.
254         */
255        SERVER_CONFIGURATION_KEYSTORE(KeyStore.class,
256                String.class),
257        ;
258
259        private final List<String> myParameterTypes;
260        private final Class<?> myReturnType;
261        private final ExceptionHandlingSpec myExceptionHandlingSpec;
262
263        CdrPointcut(@Nonnull Class<?> theReturnType, @Nonnull ExceptionHandlingSpec theExceptionHandlingSpec, String... theParameterTypes) {
264                myReturnType = theReturnType;
265                myExceptionHandlingSpec = theExceptionHandlingSpec;
266                myParameterTypes = Collections.unmodifiableList(Arrays.asList(theParameterTypes));
267        }
268
269        CdrPointcut(@Nonnull Class<?> theReturnType, String... theParameterTypes) {
270                this(theReturnType, new ExceptionHandlingSpec(), theParameterTypes);
271        }
272
273        CdrPointcut(@Nonnull Class<?> theReturnType, Class<?>... theParameterTypes) {
274                this(theReturnType, new ExceptionHandlingSpec(), toNames(theParameterTypes));
275        }
276
277        CdrPointcut(@Nonnull Class<?> theReturnType) {
278                this(theReturnType, new ExceptionHandlingSpec(), ArrayUtils.EMPTY_STRING_ARRAY);
279        }
280
281        private static String[] toNames(Class<?>[] theParameterTypes) {
282                return Arrays.stream(theParameterTypes).map(t -> t.getName()).collect(Collectors.toList()).toArray(new String[0]);
283        }
284
285        private static Class<?> toReturnTypeClass(String theReturnType) {
286                try {
287                        return Class.forName(theReturnType);
288                } catch (ClassNotFoundException theE) {
289                        return UnknownType.class;
290                }
291        }
292
293        @Override
294        public boolean isShouldLogAndSwallowException(@Nonnull Throwable theException) {
295                for (Class<? extends Throwable> next : myExceptionHandlingSpec.myTypesToLogAndSwallow) {
296                        if (next.isAssignableFrom(theException.getClass())) {
297                                return true;
298                        }
299                }
300                return false;
301        }
302
303        @Override
304        @Nonnull
305        public Class<?> getReturnType() {
306                return myReturnType;
307        }
308
309        @Override
310        @Nonnull
311        public List<String> getParameterTypes() {
312                return myParameterTypes;
313        }
314
315        private static class UnknownType {
316        }
317
318        private static class ExceptionHandlingSpec {
319
320                private final Set<Class<? extends Throwable>> myTypesToLogAndSwallow = new HashSet<>();
321
322                ExceptionHandlingSpec addLogAndSwallow(@Nonnull Class<? extends Throwable> theType) {
323                        myTypesToLogAndSwallow.add(theType);
324                        return this;
325                }
326
327        }
328
329}