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}