001package ca.uhn.fhir.test.utilities;
002
003/*-
004 * #%L
005 * HAPI FHIR Test Utilities
006 * %%
007 * Copyright (C) 2014 - 2023 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.context.FhirContext;
024import ca.uhn.fhir.i18n.Msg;
025import ca.uhn.fhir.interceptor.api.HookParams;
026import ca.uhn.fhir.interceptor.api.IInterceptorService;
027import ca.uhn.fhir.rest.api.MethodOutcome;
028import ca.uhn.fhir.rest.api.server.RequestDetails;
029import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
030import ca.uhn.fhir.rest.server.IResourceProvider;
031import ca.uhn.fhir.rest.server.IServerAddressStrategy;
032import ca.uhn.fhir.rest.server.RestfulServer;
033import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
034import ca.uhn.fhir.rest.server.provider.HashMapResourceProvider;
035import org.eclipse.jetty.server.Request;
036import org.hl7.fhir.instance.model.api.IBaseBundle;
037import org.hl7.fhir.instance.model.api.IBaseResource;
038import org.hl7.fhir.instance.model.api.IIdType;
039import org.hl7.fhir.r4.model.Bundle;
040import org.hl7.fhir.r4.model.ConceptMap;
041import org.hl7.fhir.r4.model.Observation;
042import org.hl7.fhir.r4.model.Organization;
043import org.hl7.fhir.r4.model.Patient;
044import org.hl7.fhir.r4.model.Reference;
045import org.junit.jupiter.api.extension.AfterEachCallback;
046import org.junit.jupiter.api.extension.BeforeEachCallback;
047import org.junit.jupiter.api.extension.ExtensionContext;
048
049import javax.servlet.ServletException;
050import javax.servlet.http.HttpServletRequest;
051import javax.servlet.http.HttpServletResponse;
052import java.io.IOException;
053import java.util.ArrayList;
054import java.util.Collections;
055import java.util.Enumeration;
056import java.util.HashMap;
057import java.util.List;
058import java.util.Map;
059import java.util.stream.Collectors;
060
061public class RestServerR4Helper extends BaseRestServerHelper implements BeforeEachCallback, AfterEachCallback {
062        protected final MyRestfulServer myRestServer;
063
064        public RestServerR4Helper() {
065                this(false);
066        }
067
068        public RestServerR4Helper(boolean theInitialize) {
069                super(FhirContext.forR4Cached());
070                myRestServer = new MyRestfulServer(myFhirContext);
071                if (theInitialize) {
072                        try {
073                                myRestServer.initialize();
074                        } catch (ServletException e) {
075                                throw new RuntimeException(Msg.code(2110) + "Failed to initialize server", e);
076                        }
077                }
078        }
079
080        @Override
081        public void beforeEach(ExtensionContext context) throws Exception {
082                startServer(myRestServer);
083        }
084
085        @Override
086        public void afterEach(ExtensionContext context) throws Exception {
087                super.afterEach();
088                myRestServer.getInterceptorService().unregisterAllAnonymousInterceptors();
089                myRestServer.clearDataAndCounts();
090        }
091
092        public List<Bundle> getTransactions() {
093                List<IBaseBundle> transactions = myRestServer.getPlainProvider().getTransactions();
094
095                // Make a copy to avoid synchronization issues
096                transactions = new ArrayList<>(transactions);
097
098                return transactions
099                        .stream()
100                        .map(t -> (Bundle) t)
101                        .collect(Collectors.toList());
102        }
103
104        @Override
105        public void clearDataAndCounts() {
106                myRestServer.clearDataAndCounts();
107        }
108
109        @Override
110        public void setFailNextPut(boolean theFailNextPut) {
111                myRestServer.setFailNextPut(theFailNextPut);
112        }
113
114        @Override
115        public List<Object> getInterceptors() {
116                return myRestServer.getInterceptorService().getAllRegisteredInterceptors();
117        }
118
119        @Override
120        public void unregisterInterceptor(Object theInterceptor) {
121                myRestServer.getInterceptorService().unregisterInterceptor(theInterceptor);
122        }
123
124        @Override
125        public void clearCounts() {
126                myRestServer.clearCounts();
127        }
128
129        @Override
130        public long getPatientCountSearch() {
131                return myRestServer.getPatientResourceProvider().getCountSearch();
132        }
133
134        @Override
135        public long getPatientCountDelete() {
136                return myRestServer.getPatientResourceProvider().getCountDelete();
137        }
138
139        @Override
140        public long getPatientCountUpdate() {
141                return myRestServer.getPatientResourceProvider().getCountUpdate();
142        }
143
144        @Override
145        public long getPatientCountRead() {
146                return myRestServer.getPatientResourceProvider().getCountRead();
147        }
148
149        @Override
150        public long getObservationCountSearch() {
151                return myRestServer.getObservationResourceProvider().getCountSearch();
152        }
153
154        @Override
155        public long getObservationCountDelete() {
156                return myRestServer.getObservationResourceProvider().getCountDelete();
157        }
158
159        @Override
160        public long getObservationCountUpdate() {
161                return myRestServer.getObservationResourceProvider().getCountUpdate();
162        }
163
164        @Override
165        public long getObservationCountRead() {
166                return myRestServer.getObservationResourceProvider().getCountRead();
167        }
168
169        @Override
170        public boolean registerInterceptor(Object theInterceptor) {
171                return myRestServer.getInterceptorService().registerInterceptor(theInterceptor);
172        }
173
174        public void registerProvider(Object theProvider) {
175                myRestServer.registerProvider(theProvider);
176        }
177
178        public void setExpectedCount(int theCount) {
179                myRestServer.getPlainProvider().setExpectedCount(theCount);
180        }
181
182        public List<HookParams> awaitExpected() throws InterruptedException {
183                return myRestServer.getPlainProvider().awaitExpected();
184        }
185
186        @Override
187        public HashMapResourceProvider<Observation> getObservationResourceProvider() {
188                return myRestServer.getObservationResourceProvider();
189        }
190
191        public void setObservationResourceProvider(HashMapResourceProvider<Observation> theResourceProvider) {
192                myRestServer.setObservationResourceProvider(theResourceProvider);
193        }
194
195        @Override
196        public HashMapResourceProvider<Patient> getPatientResourceProvider() {
197                return myRestServer.getPatientResourceProvider();
198        }
199
200        @Override
201        public HashMapResourceProvider<ConceptMap> getConceptMapResourceProvider() {
202                return myRestServer.getConceptMapResourceProvider();
203        }
204
205        public void setConceptMapResourceProvider(HashMapResourceProvider<ConceptMap> theResourceProvider) {
206                myRestServer.setConceptMapResourceProvider(theResourceProvider);
207        }
208
209        @Override
210        public IIdType createPatientWithId(String theId) {
211                Patient patient = new Patient();
212                patient.setId("Patient/" + theId);
213                patient.addIdentifier().setSystem("http://foo").setValue(theId);
214                return this.createPatient(patient);
215        }
216
217        @Override
218        public IIdType createPatient(IBaseResource theBaseResource) {
219                return myRestServer.getPatientResourceProvider().store((Patient) theBaseResource);
220        }
221
222        @Override
223        public IIdType createObservationForPatient(IIdType thePatientId) {
224                Observation observation = new Observation();
225                observation.setSubject(new Reference(thePatientId));
226                return this.createObservation(observation);
227                //TODO maybe add some data to this obs?
228        }
229
230        @Override
231        public IIdType createObservation(IBaseResource theBaseResource) {
232                return myRestServer.getObservationResourceProvider().store((Observation) theBaseResource);
233        }
234
235        public List<String> getRequestUrls() {
236                return myRestServer.myRequestUrls;
237        }
238
239        public List<String> getRequestVerbs() {
240                return myRestServer.myRequestVerbs;
241        }
242
243        public List<Map<String, String>> getRequestHeaders() {
244                return myRestServer.myRequestHeaders;
245        }
246
247        public IInterceptorService getInterceptorService() {
248                return myRestServer.getInterceptorService();
249        }
250
251        @Override
252        public void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy) {
253                myRestServer.setServerAddressStrategy(theServerAddressStrategy);
254        }
255
256        private static class MyRestfulServer extends RestfulServer {
257                private final List<String> myRequestUrls = Collections.synchronizedList(new ArrayList<>());
258                private final List<String> myRequestVerbs = Collections.synchronizedList(new ArrayList<>());
259                private final List<Map<String, String>> myRequestHeaders = Collections.synchronizedList(new ArrayList<>());
260                private boolean myFailNextPut;
261                private HashMapResourceProvider<Patient> myPatientResourceProvider;
262                private HashMapResourceProvider<Observation> myObservationResourceProvider;
263                private HashMapResourceProvider<Organization> myOrganizationResourceProvider;
264                private HashMapResourceProvider<ConceptMap> myConceptMapResourceProvider;
265                private RestServerDstu3Helper.MyPlainProvider myPlainProvider;
266
267                public MyRestfulServer(FhirContext theFhirContext) {
268                        super(theFhirContext);
269                }
270
271                public RestServerDstu3Helper.MyPlainProvider getPlainProvider() {
272                        return myPlainProvider;
273                }
274
275                public void setFailNextPut(boolean theFailNextPut) {
276                        myFailNextPut = theFailNextPut;
277                }
278
279                public void clearCounts() {
280                        for (IResourceProvider next : getResourceProviders()) {
281                                if (next instanceof HashMapResourceProvider) {
282                                        HashMapResourceProvider provider = (HashMapResourceProvider) next;
283                                        provider.clearCounts();
284                                }
285                        }
286                        myPlainProvider.clear();
287                        myRequestUrls.clear();
288                        myRequestVerbs.clear();
289                }
290
291                @Override
292                protected void service(HttpServletRequest theReq, HttpServletResponse theResp) throws ServletException, IOException {
293                        Request request = (Request) theReq;
294
295                        Map<String, String> headers = pullOutHeaders(theReq);
296                        myRequestHeaders.add(headers);
297                        myRequestUrls.add(request.getOriginalURI());
298                        myRequestVerbs.add(request.getMethod());
299                        super.service(theReq, theResp);
300                }
301
302                private Map<String, String> pullOutHeaders(HttpServletRequest theReq) {
303                        Enumeration<String> headerNames = theReq.getHeaderNames();
304                        Map<String, String> headers = new HashMap<>();
305                        while (headerNames.hasMoreElements()) {
306                                String headerName = headerNames.nextElement();
307                                headers.put(headerName, theReq.getHeader(headerName));
308                        }
309                        return headers;
310                }
311
312                public void clearDataAndCounts() {
313                        for (IResourceProvider next : getResourceProviders()) {
314                                if (next instanceof HashMapResourceProvider) {
315                                        HashMapResourceProvider provider = (HashMapResourceProvider) next;
316                                        provider.clear();
317                                }
318                        }
319                        clearCounts();
320                }
321
322                public HashMapResourceProvider<Observation> getObservationResourceProvider() {
323                        return myObservationResourceProvider;
324                }
325
326                public void setObservationResourceProvider(HashMapResourceProvider<Observation> theResourceProvider) {
327                        myObservationResourceProvider.getStoredResources().forEach(o -> theResourceProvider.store(o));
328
329                        unregisterProvider(myObservationResourceProvider);
330                        registerProvider(theResourceProvider);
331                        myObservationResourceProvider = theResourceProvider;
332                }
333
334                public HashMapResourceProvider<Organization> getOrganizationResourceProvider() {
335                        return myOrganizationResourceProvider;
336                }
337
338                public HashMapResourceProvider<ConceptMap> getConceptMapResourceProvider() {
339                        return myConceptMapResourceProvider;
340                }
341
342                public void setConceptMapResourceProvider(HashMapResourceProvider<ConceptMap> theResourceProvider) {
343                        myConceptMapResourceProvider.getStoredResources().forEach(c -> theResourceProvider.store(c));
344
345                        unregisterProvider(myConceptMapResourceProvider);
346                        registerProvider(theResourceProvider);
347                        myConceptMapResourceProvider = theResourceProvider;
348                }
349
350                public HashMapResourceProvider<Patient> getPatientResourceProvider() {
351                        return myPatientResourceProvider;
352                }
353
354                @Override
355                protected void initialize() throws ServletException {
356                        super.initialize();
357
358                        FhirContext fhirContext = getFhirContext();
359                        myPatientResourceProvider = new MyHashMapResourceProvider(fhirContext, Patient.class);
360                        registerProvider(myPatientResourceProvider);
361                        myObservationResourceProvider = new MyHashMapResourceProvider(fhirContext, Observation.class);
362                        registerProvider(myObservationResourceProvider);
363                        myOrganizationResourceProvider = new MyHashMapResourceProvider(fhirContext, Organization.class);
364                        registerProvider(myOrganizationResourceProvider);
365                        myConceptMapResourceProvider = new MyHashMapResourceProvider(fhirContext, ConceptMap.class);
366                        registerProvider(myConceptMapResourceProvider);
367
368                        myPlainProvider = new RestServerDstu3Helper.MyPlainProvider();
369                        registerProvider(myPlainProvider);
370
371                        setPagingProvider(new FifoMemoryPagingProvider(20));
372                }
373
374                public class MyHashMapResourceProvider<T extends IBaseResource> extends HashMapResourceProvider<T> {
375                        public MyHashMapResourceProvider(FhirContext theContext, Class theType) {
376                                super(theContext, theType);
377                        }
378
379                        @Override
380                        public MethodOutcome update(T theResource, String theConditional, RequestDetails theRequestDetails) {
381                                if (myFailNextPut) {
382                                        throw new PreconditionFailedException(Msg.code(2111) + "Failed update operation");
383                                }
384                                return super.update(theResource, theConditional, theRequestDetails);
385                        }
386                }
387        }
388}