001package ca.uhn.fhir.rest.client; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2016 University Health Network 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 */ 022import static org.apache.commons.lang3.StringUtils.isBlank; 023import static org.apache.commons.lang3.StringUtils.isNotBlank; 024 025import java.io.IOException; 026import java.io.Reader; 027import java.util.ArrayList; 028import java.util.Arrays; 029import java.util.Date; 030import java.util.HashMap; 031import java.util.HashSet; 032import java.util.LinkedHashMap; 033import java.util.List; 034import java.util.Map; 035import java.util.Map.Entry; 036import java.util.Set; 037 038import org.apache.commons.io.IOUtils; 039import org.apache.commons.lang3.StringUtils; 040import org.apache.commons.lang3.Validate; 041import org.apache.http.client.HttpClient; 042import org.apache.http.client.methods.HttpRequestBase; 043import org.hl7.fhir.instance.model.api.IBase; 044import org.hl7.fhir.instance.model.api.IBaseBundle; 045import org.hl7.fhir.instance.model.api.IBaseConformance; 046import org.hl7.fhir.instance.model.api.IBaseDatatype; 047import org.hl7.fhir.instance.model.api.IBaseMetaType; 048import org.hl7.fhir.instance.model.api.IBaseParameters; 049import org.hl7.fhir.instance.model.api.IBaseResource; 050import org.hl7.fhir.instance.model.api.IIdType; 051import org.hl7.fhir.instance.model.api.IPrimitiveType; 052 053import ca.uhn.fhir.context.BaseRuntimeChildDefinition; 054import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; 055import ca.uhn.fhir.context.BaseRuntimeElementDefinition; 056import ca.uhn.fhir.context.FhirContext; 057import ca.uhn.fhir.context.FhirVersionEnum; 058import ca.uhn.fhir.context.IRuntimeDatatypeDefinition; 059import ca.uhn.fhir.context.RuntimeResourceDefinition; 060import ca.uhn.fhir.model.api.Bundle; 061import ca.uhn.fhir.model.api.IQueryParameterType; 062import ca.uhn.fhir.model.api.Include; 063import ca.uhn.fhir.model.api.TagList; 064import ca.uhn.fhir.model.base.resource.BaseConformance; 065import ca.uhn.fhir.model.base.resource.BaseOperationOutcome; 066import ca.uhn.fhir.model.primitive.DateTimeDt; 067import ca.uhn.fhir.model.primitive.IdDt; 068import ca.uhn.fhir.model.primitive.InstantDt; 069import ca.uhn.fhir.model.primitive.UriDt; 070import ca.uhn.fhir.parser.DataFormatException; 071import ca.uhn.fhir.parser.IParser; 072import ca.uhn.fhir.rest.api.MethodOutcome; 073import ca.uhn.fhir.rest.api.PreferReturnEnum; 074import ca.uhn.fhir.rest.api.SummaryEnum; 075import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; 076import ca.uhn.fhir.rest.gclient.IClientExecutable; 077import ca.uhn.fhir.rest.gclient.ICreate; 078import ca.uhn.fhir.rest.gclient.ICreateTyped; 079import ca.uhn.fhir.rest.gclient.ICreateWithQuery; 080import ca.uhn.fhir.rest.gclient.ICreateWithQueryTyped; 081import ca.uhn.fhir.rest.gclient.ICriterion; 082import ca.uhn.fhir.rest.gclient.ICriterionInternal; 083import ca.uhn.fhir.rest.gclient.IDelete; 084import ca.uhn.fhir.rest.gclient.IDeleteTyped; 085import ca.uhn.fhir.rest.gclient.IDeleteWithQuery; 086import ca.uhn.fhir.rest.gclient.IDeleteWithQueryTyped; 087import ca.uhn.fhir.rest.gclient.IFetchConformanceTyped; 088import ca.uhn.fhir.rest.gclient.IFetchConformanceUntyped; 089import ca.uhn.fhir.rest.gclient.IGetPage; 090import ca.uhn.fhir.rest.gclient.IGetPageTyped; 091import ca.uhn.fhir.rest.gclient.IGetPageUntyped; 092import ca.uhn.fhir.rest.gclient.IGetTags; 093import ca.uhn.fhir.rest.gclient.IHistory; 094import ca.uhn.fhir.rest.gclient.IHistoryTyped; 095import ca.uhn.fhir.rest.gclient.IHistoryUntyped; 096import ca.uhn.fhir.rest.gclient.IMeta; 097import ca.uhn.fhir.rest.gclient.IMetaAddOrDeleteSourced; 098import ca.uhn.fhir.rest.gclient.IMetaAddOrDeleteUnsourced; 099import ca.uhn.fhir.rest.gclient.IMetaGetUnsourced; 100import ca.uhn.fhir.rest.gclient.IOperation; 101import ca.uhn.fhir.rest.gclient.IOperationUnnamed; 102import ca.uhn.fhir.rest.gclient.IOperationUntyped; 103import ca.uhn.fhir.rest.gclient.IOperationUntypedWithInput; 104import ca.uhn.fhir.rest.gclient.IOperationUntypedWithInputAndPartialOutput; 105import ca.uhn.fhir.rest.gclient.IParam; 106import ca.uhn.fhir.rest.gclient.IQuery; 107import ca.uhn.fhir.rest.gclient.IRead; 108import ca.uhn.fhir.rest.gclient.IReadExecutable; 109import ca.uhn.fhir.rest.gclient.IReadIfNoneMatch; 110import ca.uhn.fhir.rest.gclient.IReadTyped; 111import ca.uhn.fhir.rest.gclient.ISort; 112import ca.uhn.fhir.rest.gclient.ITransaction; 113import ca.uhn.fhir.rest.gclient.ITransactionTyped; 114import ca.uhn.fhir.rest.gclient.IUntypedQuery; 115import ca.uhn.fhir.rest.gclient.IUpdate; 116import ca.uhn.fhir.rest.gclient.IUpdateExecutable; 117import ca.uhn.fhir.rest.gclient.IUpdateTyped; 118import ca.uhn.fhir.rest.gclient.IUpdateWithQuery; 119import ca.uhn.fhir.rest.gclient.IUpdateWithQueryTyped; 120import ca.uhn.fhir.rest.gclient.IValidate; 121import ca.uhn.fhir.rest.gclient.IValidateUntyped; 122import ca.uhn.fhir.rest.method.DeleteMethodBinding; 123import ca.uhn.fhir.rest.method.HistoryMethodBinding; 124import ca.uhn.fhir.rest.method.HttpDeleteClientInvocation; 125import ca.uhn.fhir.rest.method.HttpGetClientInvocation; 126import ca.uhn.fhir.rest.method.HttpSimpleGetClientInvocation; 127import ca.uhn.fhir.rest.method.IClientResponseHandler; 128import ca.uhn.fhir.rest.method.MethodUtil; 129import ca.uhn.fhir.rest.method.OperationMethodBinding; 130import ca.uhn.fhir.rest.method.ReadMethodBinding; 131import ca.uhn.fhir.rest.method.SearchMethodBinding; 132import ca.uhn.fhir.rest.method.SearchStyleEnum; 133import ca.uhn.fhir.rest.method.TransactionMethodBinding; 134import ca.uhn.fhir.rest.method.ValidateMethodBindingDstu1; 135import ca.uhn.fhir.rest.method.ValidateMethodBindingDstu2; 136import ca.uhn.fhir.rest.param.DateParam; 137import ca.uhn.fhir.rest.param.DateRangeParam; 138import ca.uhn.fhir.rest.param.TokenParam; 139import ca.uhn.fhir.rest.server.Constants; 140import ca.uhn.fhir.rest.server.EncodingEnum; 141import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory; 142import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; 143import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 144import ca.uhn.fhir.rest.server.exceptions.NotModifiedException; 145import ca.uhn.fhir.util.ICallable; 146import ca.uhn.fhir.util.ParametersUtil; 147import ca.uhn.fhir.util.UrlUtil; 148 149/** 150 * @author James Agnew 151 * @author Doug Martin (Regenstrief Center for Biomedical Informatics) 152 */ 153public class GenericClient extends BaseClient implements IGenericClient { 154 155 private static final String I18N_CANNOT_DETEMINE_RESOURCE_TYPE = "ca.uhn.fhir.rest.client.GenericClient.cannotDetermineResourceTypeFromUri"; 156 private static final String I18N_INCOMPLETE_URI_FOR_READ = "ca.uhn.fhir.rest.client.GenericClient.incompleteUriForRead"; 157 private static final String I18N_NO_VERSION_ID_FOR_VREAD = "ca.uhn.fhir.rest.client.GenericClient.noVersionIdForVread"; 158 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClient.class); 159 private FhirContext myContext; 160 private HttpRequestBase myLastRequest; 161 private boolean myLogRequestAndResponse; 162 163 /** 164 * For now, this is a part of the internal API of HAPI - Use with caution as this method may change! 165 */ 166 public GenericClient(FhirContext theContext, HttpClient theHttpClient, String theServerBase, RestfulClientFactory theFactory) { 167 super(theHttpClient, theServerBase, theFactory); 168 myContext = theContext; 169 } 170 171 @Override 172 public BaseConformance conformance() { 173 if (myContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU2_HL7ORG)) { 174 throw new IllegalArgumentException("Must call conformance(" + IBaseConformance.class.getSimpleName() + ") instead of conformance() for HL7.org structures"); 175 } 176 177 HttpGetClientInvocation invocation = MethodUtil.createConformanceInvocation(); 178 if (isKeepResponses()) { 179 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); 180 } 181 182 @SuppressWarnings("unchecked") 183 Class<BaseConformance> conformance = (Class<BaseConformance>) myContext.getResourceDefinition("Conformance").getImplementingClass(); 184 185 ResourceResponseHandler<? extends BaseConformance> binding = new ResourceResponseHandler<BaseConformance>(conformance, null); 186 BaseConformance resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse); 187 return resp; 188 } 189 190 @Override 191 public ICreate create() { 192 return new CreateInternal(); 193 } 194 195 @Override 196 public MethodOutcome create(IBaseResource theResource) { 197 BaseHttpClientInvocation invocation = MethodUtil.createCreateInvocation(theResource, myContext); 198 if (isKeepResponses()) { 199 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); 200 } 201 202 RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); 203 final String resourceName = def.getName(); 204 205 OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName); 206 207 MethodOutcome resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse); 208 return resp; 209 210 } 211 212 @Override 213 public IDelete delete() { 214 return new DeleteInternal(); 215 } 216 217 @Override 218 public MethodOutcome delete(final Class<? extends IBaseResource> theType, IdDt theId) { 219 HttpDeleteClientInvocation invocation = DeleteMethodBinding.createDeleteInvocation(theId.withResourceType(toResourceName(theType))); 220 if (isKeepResponses()) { 221 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); 222 } 223 224 final String resourceName = myContext.getResourceDefinition(theType).getName(); 225 OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName); 226 MethodOutcome resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse); 227 return resp; 228 } 229 230 @Override 231 public MethodOutcome delete(Class<? extends IBaseResource> theType, String theId) { 232 return delete(theType, new IdDt(theId)); 233 } 234 235 private <T extends IBaseResource> T doReadOrVRead(final Class<T> theType, IIdType theId, boolean theVRead, ICallable<T> theNotModifiedHandler, String theIfVersionMatches, Boolean thePrettyPrint, 236 SummaryEnum theSummary, EncodingEnum theEncoding, Set<String> theSubsetElements) { 237 String resName = toResourceName(theType); 238 IIdType id = theId; 239 if (!id.hasBaseUrl()) { 240 id = new IdDt(resName, id.getIdPart(), id.getVersionIdPart()); 241 } 242 243 HttpGetClientInvocation invocation; 244 if (id.hasBaseUrl()) { 245 if (theVRead) { 246 invocation = ReadMethodBinding.createAbsoluteVReadInvocation(id); 247 } else { 248 invocation = ReadMethodBinding.createAbsoluteReadInvocation(id); 249 } 250 } else { 251 if (theVRead) { 252 invocation = ReadMethodBinding.createVReadInvocation(id, resName); 253 } else { 254 invocation = ReadMethodBinding.createReadInvocation(id, resName); 255 } 256 } 257 if (isKeepResponses()) { 258 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); 259 } 260 261 if (theIfVersionMatches != null) { 262 invocation.addHeader(Constants.HEADER_IF_NONE_MATCH, '"' + theIfVersionMatches + '"'); 263 } 264 265 boolean allowHtmlResponse = (theSummary == SummaryEnum.TEXT) || (theSummary == null && getSummary() == SummaryEnum.TEXT); 266 ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theType, id, allowHtmlResponse); 267 268 if (theNotModifiedHandler == null) { 269 return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements); 270 } else { 271 try { 272 return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements); 273 } catch (NotModifiedException e) { 274 return theNotModifiedHandler.call(); 275 } 276 } 277 278 } 279 280 @Override 281 public IFetchConformanceUntyped fetchConformance() { 282 return new FetchConformanceInternal(); 283 } 284 285 // public IResource read(UriDt url) { 286 // return read(inferResourceClass(url), url); 287 // } 288 // 289 // @SuppressWarnings("unchecked") 290 // public <T extends IResource> T read(final Class<T> theType, UriDt url) { 291 // return (T) invoke(theType, url, new ResourceResponseHandler<T>(theType)); 292 // } 293 // 294 // public Bundle search(UriDt url) { 295 // return search(inferResourceClass(url), url); 296 // } 297 298 @Override 299 public void forceConformanceCheck() { 300 super.forceConformanceCheck(); 301 } 302 303 @Override 304 public FhirContext getFhirContext() { 305 return myContext; 306 } 307 308 public HttpRequestBase getLastRequest() { 309 return myLastRequest; 310 } 311 312 protected String getPreferredId(IBaseResource theResource, String theId) { 313 if (isNotBlank(theId)) { 314 return theId; 315 } 316 return theResource.getIdElement().getIdPart(); 317 } 318 319 @Override 320 public IGetTags getTags() { 321 return new GetTagsInternal(); 322 } 323 324 @Override 325 public IHistory history() { 326 return new HistoryInternal(); 327 } 328 329 @Override 330 public <T extends IBaseResource> Bundle history(final Class<T> theType, IdDt theIdDt, DateTimeDt theSince, Integer theLimit) { 331 String resourceName = theType != null ? toResourceName(theType) : null; 332 String id = theIdDt != null && theIdDt.isEmpty() == false ? theIdDt.getValue() : null; 333 HttpGetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(resourceName, id, theSince, theLimit); 334 if (isKeepResponses()) { 335 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); 336 } 337 338 BundleResponseHandler binding = new BundleResponseHandler(theType); 339 Bundle resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse); 340 return resp; 341 342 } 343 344 @Override 345 public <T extends IBaseResource> Bundle history(Class<T> theType, String theId, DateTimeDt theSince, Integer theLimit) { 346 return history(theType, new IdDt(theId), theSince, theLimit); 347 } 348 349 private Class<? extends IBaseResource> inferResourceClass(UriDt theUrl) { 350 String urlString = theUrl.getValueAsString(); 351 int i = urlString.indexOf('?'); 352 353 if (i >= 0) { 354 urlString = urlString.substring(0, i); 355 } 356 357 i = urlString.indexOf("://"); 358 359 if (i >= 0) { 360 urlString = urlString.substring(i + 3); 361 } 362 363 String[] pcs = urlString.split("\\/"); 364 365 for (i = pcs.length - 1; i >= 0; i--) { 366 String s = pcs[i].trim(); 367 368 if (!s.isEmpty()) { 369 RuntimeResourceDefinition def = myContext.getResourceDefinition(s); 370 if (def != null) { 371 return def.getImplementingClass(); 372 } 373 } 374 } 375 376 throw new RuntimeException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theUrl.getValueAsString())); 377 378 } 379 380 // @Override 381 // public <T extends IBaseResource> T read(final Class<T> theType, IdDt theId) { 382 // return doReadOrVRead(theType, theId, false, null, null); 383 // } 384 385 public boolean isLogRequestAndResponse() { 386 return myLogRequestAndResponse; 387 } 388 389 @Override 390 public IGetPage loadPage() { 391 return new LoadPageInternal(); 392 } 393 394 @Override 395 public IMeta meta() { 396 if (myContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU1)) { 397 throw new IllegalStateException("Can not call $meta operations on a DSTU1 client"); 398 } 399 return new MetaInternal(); 400 } 401 402 @Override 403 public IOperation operation() { 404 if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1) == false) { 405 throw new IllegalStateException("Operations are only supported in FHIR DSTU2 and later. This client was created using a context configured for " + myContext.getVersion().getVersion().name()); 406 } 407 return new OperationInternal(); 408 } 409 410 @Override 411 public IRead read() { 412 return new ReadInternal(); 413 } 414 415 @Override 416 public <T extends IBaseResource> T read(Class<T> theType, String theId) { 417 return read(theType, new IdDt(theId)); 418 } 419 420 @Override 421 public <T extends IBaseResource> T read(final Class<T> theType, UriDt theUrl) { 422 IdDt id = theUrl instanceof IdDt ? ((IdDt) theUrl) : new IdDt(theUrl); 423 return doReadOrVRead(theType, id, false, null, null, false, null, null, null); 424 } 425 426 @Override 427 public IBaseResource read(UriDt theUrl) { 428 IdDt id = new IdDt(theUrl); 429 String resourceType = id.getResourceType(); 430 if (isBlank(resourceType)) { 431 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_INCOMPLETE_URI_FOR_READ, theUrl.getValueAsString())); 432 } 433 RuntimeResourceDefinition def = myContext.getResourceDefinition(resourceType); 434 if (def == null) { 435 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theUrl.getValueAsString())); 436 } 437 return read(def.getImplementingClass(), id); 438 } 439 440 @Override 441 public IUntypedQuery search() { 442 return new SearchInternal(); 443 } 444 445 @Override 446 public <T extends IBaseResource> Bundle search(final Class<T> theType, Map<String, List<IQueryParameterType>> theParams) { 447 LinkedHashMap<String, List<String>> params = new LinkedHashMap<String, List<String>>(); 448 for (Entry<String, List<IQueryParameterType>> nextEntry : theParams.entrySet()) { 449 ArrayList<String> valueList = new ArrayList<String>(); 450 String qualifier = null; 451 for (IQueryParameterType nextValue : nextEntry.getValue()) { 452 valueList.add(nextValue.getValueAsQueryToken()); 453 qualifier = nextValue.getQueryParameterQualifier(); 454 } 455 qualifier = StringUtils.defaultString(qualifier); 456 params.put(nextEntry.getKey() + qualifier, valueList); 457 } 458 459 BaseHttpClientInvocation invocation = SearchMethodBinding.createSearchInvocation(myContext, toResourceName(theType), params, null, null, null); 460 if (isKeepResponses()) { 461 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); 462 } 463 464 BundleResponseHandler binding = new BundleResponseHandler(theType); 465 Bundle resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse); 466 return resp; 467 } 468 469 @Override 470 public <T extends IBaseResource> Bundle search(final Class<T> theType, UriDt theUrl) { 471 BaseHttpClientInvocation invocation = new HttpGetClientInvocation(theUrl.getValueAsString()); 472 return invokeClient(myContext, new BundleResponseHandler(theType), invocation); 473 } 474 475 @Override 476 public Bundle search(UriDt theUrl) { 477 return search(inferResourceClass(theUrl), theUrl); 478 } 479 480 /** 481 * For now, this is a part of the internal API of HAPI - Use with caution as this method may change! 482 */ 483 public void setLastRequest(HttpRequestBase theLastRequest) { 484 myLastRequest = theLastRequest; 485 } 486 487 @Override 488 public void setLogRequestAndResponse(boolean theLogRequestAndResponse) { 489 myLogRequestAndResponse = theLogRequestAndResponse; 490 } 491 492 private String toResourceName(Class<? extends IBaseResource> theType) { 493 return myContext.getResourceDefinition(theType).getName(); 494 } 495 496 @Override 497 public ITransaction transaction() { 498 return new TransactionInternal(); 499 } 500 501 @Override 502 public List<IBaseResource> transaction(List<IBaseResource> theResources) { 503 BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(theResources, myContext); 504 if (isKeepResponses()) { 505 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); 506 } 507 508 Bundle resp = invokeClient(myContext, new BundleResponseHandler(null), invocation, myLogRequestAndResponse); 509 510 return new ArrayList<IBaseResource>(resp.toListOfResources()); 511 } 512 513 @Override 514 public IUpdate update() { 515 return new UpdateInternal(); 516 } 517 518 @Override 519 public MethodOutcome update(IdDt theIdDt, IBaseResource theResource) { 520 BaseHttpClientInvocation invocation = MethodUtil.createUpdateInvocation(theResource, null, theIdDt, myContext); 521 if (isKeepResponses()) { 522 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); 523 } 524 525 RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); 526 final String resourceName = def.getName(); 527 528 OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName); 529 MethodOutcome resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse); 530 return resp; 531 } 532 533 @Override 534 public MethodOutcome update(String theId, IBaseResource theResource) { 535 return update(new IdDt(theId), theResource); 536 } 537 538 @Override 539 public IValidate validate() { 540 return new ValidateInternal(); 541 } 542 543 @Override 544 public MethodOutcome validate(IBaseResource theResource) { 545 BaseHttpClientInvocation invocation; 546 if (myContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU1)) { 547 invocation = ValidateMethodBindingDstu1.createValidateInvocation(theResource, null, myContext); 548 } else { 549 invocation = ValidateMethodBindingDstu2.createValidateInvocation(myContext, theResource); 550 } 551 552 if (isKeepResponses()) { 553 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); 554 } 555 556 RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); 557 final String resourceName = def.getName(); 558 559 OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName); 560 MethodOutcome resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse); 561 return resp; 562 } 563 564 @Override 565 public <T extends IBaseResource> T vread(final Class<T> theType, IdDt theId) { 566 if (theId.hasVersionIdPart() == false) { 567 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_NO_VERSION_ID_FOR_VREAD, theId.getValue())); 568 } 569 return doReadOrVRead(theType, theId, true, null, null, false, null, null, null); 570 } 571 572 /* also deprecated in interface */ 573 @Deprecated 574 @Override 575 public <T extends IBaseResource> T vread(final Class<T> theType, IdDt theId, IdDt theVersionId) { 576 return vread(theType, theId.withVersion(theVersionId.getIdPart())); 577 } 578 579 @Override 580 public <T extends IBaseResource> T vread(Class<T> theType, String theId, String theVersionId) { 581 IdDt resId = new IdDt(toResourceName(theType), theId, theVersionId); 582 return vread(theType, resId); 583 } 584 585 private static void addParam(Map<String, List<String>> params, String parameterName, String parameterValue) { 586 if (!params.containsKey(parameterName)) { 587 params.put(parameterName, new ArrayList<String>()); 588 } 589 params.get(parameterName).add(parameterValue); 590 } 591 592 private static void addPreferHeader(PreferReturnEnum thePrefer, BaseHttpClientInvocation theInvocation) { 593 if (thePrefer != null) { 594 theInvocation.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + '=' + thePrefer.getHeaderValue()); 595 } 596 } 597 598 private abstract class BaseClientExecutable<T extends IClientExecutable<?, ?>, Y> implements IClientExecutable<T, Y> { 599 protected EncodingEnum myParamEncoding; 600 protected Boolean myPrettyPrint; 601 private boolean myQueryLogRequestAndResponse; 602 private HashSet<String> mySubsetElements; 603 protected SummaryEnum mySummaryMode; 604 605 @SuppressWarnings("unchecked") 606 @Override 607 public T andLogRequestAndResponse(boolean theLogRequestAndResponse) { 608 myQueryLogRequestAndResponse = theLogRequestAndResponse; 609 return (T) this; 610 } 611 612 @SuppressWarnings("unchecked") 613 @Override 614 public T encodedJson() { 615 myParamEncoding = EncodingEnum.JSON; 616 return (T) this; 617 } 618 619 @SuppressWarnings("unchecked") 620 @Override 621 public T encodedXml() { 622 myParamEncoding = EncodingEnum.XML; 623 return (T) this; 624 } 625 626 protected EncodingEnum getParamEncoding() { 627 return myParamEncoding; 628 } 629 630 protected HashSet<String> getSubsetElements() { 631 return mySubsetElements; 632 } 633 634 protected <Z> Z invoke(Map<String, List<String>> theParams, IClientResponseHandler<Z> theHandler, BaseHttpClientInvocation theInvocation) { 635 // if (myParamEncoding != null) { 636 // theParams.put(Constants.PARAM_FORMAT, Collections.singletonList(myParamEncoding.getFormatContentType())); 637 // } 638 // 639 // if (myPrettyPrint != null) { 640 // theParams.put(Constants.PARAM_PRETTY, Collections.singletonList(myPrettyPrint.toString())); 641 // } 642 643 if (isKeepResponses()) { 644 myLastRequest = theInvocation.asHttpRequest(getServerBase(), theParams, getEncoding(), myPrettyPrint); 645 } 646 647 Z resp = invokeClient(myContext, theHandler, theInvocation, myParamEncoding, myPrettyPrint, myQueryLogRequestAndResponse || myLogRequestAndResponse, mySummaryMode, mySubsetElements); 648 return resp; 649 } 650 651 protected IBaseResource parseResourceBody(String theResourceBody) { 652 EncodingEnum encoding = MethodUtil.detectEncodingNoDefault(theResourceBody); 653 if (encoding == null) { 654 throw new InvalidRequestException("FHIR client can't determine resource encoding"); 655 } 656 return encoding.newParser(myContext).parseResource(theResourceBody); 657 } 658 659 @SuppressWarnings("unchecked") 660 @Override 661 public T prettyPrint() { 662 myPrettyPrint = true; 663 return (T) this; 664 } 665 666 @SuppressWarnings("unchecked") 667 @Override 668 public T elementsSubset(String... theElements) { 669 if (theElements != null && theElements.length > 0) { 670 mySubsetElements = new HashSet<String>(Arrays.asList(theElements)); 671 } else { 672 mySubsetElements = null; 673 } 674 return (T) this; 675 } 676 677 @SuppressWarnings("unchecked") 678 @Override 679 public T summaryMode(SummaryEnum theSummary) { 680 mySummaryMode = theSummary; 681 return ((T) this); 682 } 683 684 } 685 686 private final class BundleResponseHandler implements IClientResponseHandler<Bundle> { 687 688 private Class<? extends IBaseResource> myType; 689 690 public BundleResponseHandler(Class<? extends IBaseResource> theType) { 691 myType = theType; 692 } 693 694 @Override 695 public Bundle invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException { 696 EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType); 697 if (respType == null) { 698 throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader); 699 } 700 IParser parser = respType.newParser(myContext); 701 return parser.parseBundle(myType, theResponseReader); 702 } 703 } 704 705 private class CreateInternal extends BaseClientExecutable<ICreateTyped, MethodOutcome>implements ICreate, ICreateTyped, ICreateWithQuery, ICreateWithQueryTyped { 706 707 private CriterionList myCriterionList; 708 private String myId; 709 private PreferReturnEnum myPrefer; 710 private IBaseResource myResource; 711 private String myResourceBody; 712 private String mySearchUrl; 713 714 @Override 715 public ICreateWithQueryTyped and(ICriterion<?> theCriterion) { 716 myCriterionList.add((ICriterionInternal) theCriterion); 717 return this; 718 } 719 720 @Override 721 public ICreateWithQuery conditional() { 722 myCriterionList = new CriterionList(); 723 return this; 724 } 725 726 @Override 727 public ICreateTyped conditionalByUrl(String theSearchUrl) { 728 mySearchUrl = validateAndEscapeConditionalUrl(theSearchUrl); 729 return this; 730 } 731 732 @Override 733 public MethodOutcome execute() { 734 if (myResource == null) { 735 myResource = parseResourceBody(myResourceBody); 736 } 737 myId = getPreferredId(myResource, myId); 738 739 // If an explicit encoding is chosen, we will re-serialize to ensure the right encoding 740 if (getParamEncoding() != null) { 741 myResourceBody = null; 742 } 743 744 BaseHttpClientInvocation invocation; 745 if (mySearchUrl != null) { 746 invocation = MethodUtil.createCreateInvocation(myResource, myResourceBody, myId, myContext, mySearchUrl); 747 } else if (myCriterionList != null) { 748 invocation = MethodUtil.createCreateInvocation(myResource, myResourceBody, myId, myContext, myCriterionList.toParamList()); 749 } else { 750 invocation = MethodUtil.createCreateInvocation(myResource, myResourceBody, myId, myContext); 751 } 752 753 addPreferHeader(myPrefer, invocation); 754 755 RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource); 756 final String resourceName = def.getName(); 757 758 OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName); 759 760 Map<String, List<String>> params = new HashMap<String, List<String>>(); 761 return invoke(params, binding, invocation); 762 763 } 764 765 @Override 766 public ICreateTyped prefer(PreferReturnEnum theReturn) { 767 myPrefer = theReturn; 768 return this; 769 } 770 771 @Override 772 public ICreateTyped resource(IBaseResource theResource) { 773 Validate.notNull(theResource, "Resource can not be null"); 774 myResource = theResource; 775 return this; 776 } 777 778 @Override 779 public ICreateTyped resource(String theResourceBody) { 780 Validate.notBlank(theResourceBody, "Body can not be null or blank"); 781 myResourceBody = theResourceBody; 782 return this; 783 } 784 785 @Override 786 public ICreateWithQueryTyped where(ICriterion<?> theCriterion) { 787 myCriterionList.add((ICriterionInternal) theCriterion); 788 return this; 789 } 790 791 @Override 792 public CreateInternal withId(IdDt theId) { 793 myId = theId.getIdPart(); 794 return this; 795 } 796 797 @Override 798 public CreateInternal withId(String theId) { 799 myId = theId; 800 return this; 801 } 802 803 } 804 805 private static class CriterionList extends ArrayList<ICriterionInternal> { 806 807 private static final long serialVersionUID = 1L; 808 809 public void populateParamList(Map<String, List<String>> theParams) { 810 for (ICriterionInternal next : this) { 811 String parameterName = next.getParameterName(); 812 String parameterValue = next.getParameterValue(); 813 addParam(theParams, parameterName, parameterValue); 814 } 815 } 816 817 public Map<String, List<String>> toParamList() { 818 LinkedHashMap<String, List<String>> retVal = new LinkedHashMap<String, List<String>>(); 819 populateParamList(retVal); 820 return retVal; 821 } 822 823 } 824 825 private class DeleteInternal extends BaseClientExecutable<IDeleteTyped, BaseOperationOutcome>implements IDelete, IDeleteTyped, IDeleteWithQuery, IDeleteWithQueryTyped { 826 827 private CriterionList myCriterionList; 828 private IIdType myId; 829 private String myResourceType; 830 private String mySearchUrl; 831 832 @Override 833 public IDeleteWithQueryTyped and(ICriterion<?> theCriterion) { 834 myCriterionList.add((ICriterionInternal) theCriterion); 835 return this; 836 } 837 838 @Override 839 public BaseOperationOutcome execute() { 840 HttpDeleteClientInvocation invocation; 841 if (myId != null) { 842 invocation = DeleteMethodBinding.createDeleteInvocation(myId); 843 } else if (myCriterionList != null) { 844 Map<String, List<String>> params = myCriterionList.toParamList(); 845 invocation = DeleteMethodBinding.createDeleteInvocation(myResourceType, params); 846 } else { 847 invocation = DeleteMethodBinding.createDeleteInvocation(mySearchUrl); 848 } 849 OperationOutcomeResponseHandler binding = new OperationOutcomeResponseHandler(); 850 Map<String, List<String>> params = new HashMap<String, List<String>>(); 851 return invoke(params, binding, invocation); 852 } 853 854 @Override 855 public IDeleteTyped resource(IBaseResource theResource) { 856 Validate.notNull(theResource, "theResource can not be null"); 857 IIdType id = theResource.getIdElement(); 858 Validate.notNull(id, "theResource.getIdElement() can not be null"); 859 if (id.hasResourceType() == false || id.hasIdPart() == false) { 860 throw new IllegalArgumentException("theResource.getId() must contain a resource type and logical ID at a minimum (e.g. Patient/1234), found: " + id.getValue()); 861 } 862 myId = id; 863 return this; 864 } 865 866 @Override 867 public IDeleteTyped resourceById(IIdType theId) { 868 Validate.notNull(theId, "theId can not be null"); 869 if (theId.hasResourceType() == false || theId.hasIdPart() == false) { 870 throw new IllegalArgumentException("theId must contain a resource type and logical ID at a minimum (e.g. Patient/1234)found: " + theId.getValue()); 871 } 872 myId = theId; 873 return this; 874 } 875 876 @Override 877 public IDeleteTyped resourceById(String theResourceType, String theLogicalId) { 878 Validate.notBlank(theResourceType, "theResourceType can not be blank/null"); 879 if (myContext.getResourceDefinition(theResourceType) == null) { 880 throw new IllegalArgumentException("Unknown resource type"); 881 } 882 Validate.notBlank(theLogicalId, "theLogicalId can not be blank/null"); 883 if (theLogicalId.contains("/")) { 884 throw new IllegalArgumentException("LogicalId can not contain '/' (should only be the logical ID portion, not a qualified ID)"); 885 } 886 myId = new IdDt(theResourceType, theLogicalId); 887 return this; 888 } 889 890 @Override 891 public IDeleteWithQuery resourceConditionalByType(String theResourceType) { 892 Validate.notBlank(theResourceType, "theResourceType can not be blank/null"); 893 if (myContext.getResourceDefinition(theResourceType) == null) { 894 throw new IllegalArgumentException("Unknown resource type: " + theResourceType); 895 } 896 myResourceType = theResourceType; 897 myCriterionList = new CriterionList(); 898 return this; 899 } 900 901 @Override 902 public IDeleteTyped resourceConditionalByUrl(String theSearchUrl) { 903 mySearchUrl = validateAndEscapeConditionalUrl(theSearchUrl); 904 return this; 905 } 906 907 @Override 908 public IDeleteWithQueryTyped where(ICriterion<?> theCriterion) { 909 myCriterionList.add((ICriterionInternal) theCriterion); 910 return this; 911 } 912 913 @Override 914 public IDeleteWithQuery resourceConditionalByType(Class<? extends IBaseResource> theResourceType) { 915 Validate.notNull(theResourceType, "theResourceType can not be null"); 916 myCriterionList = new CriterionList(); 917 myResourceType = myContext.getResourceDefinition(theResourceType).getName(); 918 return this; 919 } 920 } 921 922 @SuppressWarnings({ "rawtypes", "unchecked" }) 923 private class FetchConformanceInternal extends BaseClientExecutable implements IFetchConformanceUntyped, IFetchConformanceTyped { 924 private RuntimeResourceDefinition myType; 925 926 @Override 927 public Object execute() { 928 ResourceResponseHandler binding = new ResourceResponseHandler(myType.getImplementingClass(), null); 929 HttpGetClientInvocation invocation = MethodUtil.createConformanceInvocation(); 930 return super.invoke(null, binding, invocation); 931 } 932 933 @Override 934 public <T extends IBaseConformance> IFetchConformanceTyped<T> ofType(Class<T> theResourceType) { 935 Validate.notNull(theResourceType, "theResourceType must not be null"); 936 myType = myContext.getResourceDefinition(theResourceType); 937 if (myType == null) { 938 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theResourceType)); 939 } 940 return this; 941 } 942 943 } 944 945 @SuppressWarnings({ "unchecked", "rawtypes" }) 946 private class GetPageInternal extends BaseClientExecutable<IGetPageTyped<Object>, Object>implements IGetPageTyped<Object> { 947 948 private Class<? extends IBaseBundle> myBundleType; 949 private String myUrl; 950 951 public GetPageInternal(String theUrl) { 952 myUrl = theUrl; 953 } 954 955 public GetPageInternal(String theUrl, Class<? extends IBaseBundle> theBundleType) { 956 myUrl = theUrl; 957 myBundleType = theBundleType; 958 } 959 960 @Override 961 public Object execute() { 962 IClientResponseHandler binding; 963 if (myBundleType == null) { 964 binding = new BundleResponseHandler(null); 965 } else { 966 binding = new ResourceResponseHandler(myBundleType, null); 967 } 968 HttpSimpleGetClientInvocation invocation = new HttpSimpleGetClientInvocation(myUrl); 969 970 Map<String, List<String>> params = null; 971 return invoke(params, binding, invocation); 972 } 973 974 } 975 976 private class GetTagsInternal extends BaseClientExecutable<IGetTags, TagList>implements IGetTags { 977 978 private String myId; 979 private String myResourceName; 980 private String myVersionId; 981 982 @Override 983 public TagList execute() { 984 985 Map<String, List<String>> params = new LinkedHashMap<String, List<String>>(); 986 Map<String, List<String>> initial = createExtraParams(); 987 if (initial != null) { 988 params.putAll(initial); 989 } 990 991 TagListResponseHandler binding = new TagListResponseHandler(); 992 List<String> urlFragments = new ArrayList<String>(); 993 if (isNotBlank(myResourceName)) { 994 urlFragments.add(myResourceName); 995 if (isNotBlank(myId)) { 996 urlFragments.add(myId); 997 if (isNotBlank(myVersionId)) { 998 urlFragments.add(Constants.PARAM_HISTORY); 999 urlFragments.add(myVersionId); 1000 } 1001 } 1002 } 1003 urlFragments.add(Constants.PARAM_TAGS); 1004 1005 HttpGetClientInvocation invocation = new HttpGetClientInvocation(params, urlFragments); 1006 1007 return invoke(params, binding, invocation); 1008 1009 } 1010 1011 @Override 1012 public IGetTags forResource(Class<? extends IBaseResource> theClass) { 1013 setResourceClass(theClass); 1014 return this; 1015 } 1016 1017 @Override 1018 public IGetTags forResource(Class<? extends IBaseResource> theClass, String theId) { 1019 setResourceClass(theClass); 1020 myId = theId; 1021 return this; 1022 } 1023 1024 @Override 1025 public IGetTags forResource(Class<? extends IBaseResource> theClass, String theId, String theVersionId) { 1026 setResourceClass(theClass); 1027 myId = theId; 1028 myVersionId = theVersionId; 1029 return this; 1030 } 1031 1032 private void setResourceClass(Class<? extends IBaseResource> theClass) { 1033 if (theClass != null) { 1034 myResourceName = myContext.getResourceDefinition(theClass).getName(); 1035 } else { 1036 myResourceName = null; 1037 } 1038 } 1039 1040 } 1041 1042 @SuppressWarnings("rawtypes") 1043 private class HistoryInternal extends BaseClientExecutable implements IHistory, IHistoryUntyped, IHistoryTyped { 1044 1045 private Integer myCount; 1046 private IIdType myId; 1047 private Class<? extends IBaseBundle> myReturnType; 1048 private IPrimitiveType mySince; 1049 private Class<? extends IBaseResource> myType; 1050 1051 @SuppressWarnings("unchecked") 1052 @Override 1053 public IHistoryTyped andReturnBundle(Class theType) { 1054 myReturnType = theType; 1055 return this; 1056 } 1057 1058 @SuppressWarnings("unchecked") 1059 @Override 1060 public IHistoryTyped andReturnDstu1Bundle() { 1061 return this; 1062 } 1063 1064 @Override 1065 public IHistoryTyped count(Integer theCount) { 1066 myCount = theCount; 1067 return this; 1068 } 1069 1070 @SuppressWarnings("unchecked") 1071 @Override 1072 public Object execute() { 1073 String resourceName; 1074 String id; 1075 if (myType != null) { 1076 resourceName = myContext.getResourceDefinition(myType).getName(); 1077 id = null; 1078 } else if (myId != null) { 1079 resourceName = myId.getResourceType(); 1080 id = myId.getIdPart(); 1081 } else { 1082 resourceName = null; 1083 id = null; 1084 } 1085 1086 HttpGetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(resourceName, id, mySince, myCount); 1087 1088 IClientResponseHandler handler; 1089 if (myReturnType != null) { 1090 handler = new ResourceResponseHandler(myReturnType, null); 1091 } else { 1092 handler = new BundleResponseHandler(null); 1093 } 1094 1095 return invoke(null, handler, invocation); 1096 } 1097 1098 @Override 1099 public IHistoryUntyped onInstance(IIdType theId) { 1100 if (theId.hasResourceType() == false) { 1101 throw new IllegalArgumentException("Resource ID does not have a resource type: " + theId.getValue()); 1102 } 1103 myId = theId; 1104 return this; 1105 } 1106 1107 @Override 1108 public IHistoryUntyped onServer() { 1109 return this; 1110 } 1111 1112 @Override 1113 public IHistoryUntyped onType(Class<? extends IBaseResource> theResourceType) { 1114 myType = theResourceType; 1115 return this; 1116 } 1117 1118 @Override 1119 public IHistoryTyped since(Date theCutoff) { 1120 if (theCutoff != null) { 1121 mySince = new InstantDt(theCutoff); 1122 } else { 1123 mySince = null; 1124 } 1125 return this; 1126 } 1127 1128 @Override 1129 public IHistoryTyped since(IPrimitiveType theCutoff) { 1130 mySince = theCutoff; 1131 return this; 1132 } 1133 1134 } 1135 1136 @SuppressWarnings({ "unchecked", "rawtypes" }) 1137 private final class LoadPageInternal implements IGetPage, IGetPageUntyped { 1138 1139 private static final String PREV = "prev"; 1140 private static final String PREVIOUS = "previous"; 1141 private String myPageUrl; 1142 1143 @Override 1144 public <T extends IBaseBundle> IGetPageTyped andReturnBundle(Class<T> theBundleType) { 1145 Validate.notNull(theBundleType, "theBundleType must not be null"); 1146 return new GetPageInternal(myPageUrl, theBundleType); 1147 } 1148 1149 @Override 1150 public IGetPageTyped andReturnDstu1Bundle() { 1151 return new GetPageInternal(myPageUrl); 1152 } 1153 1154 @Override 1155 public IGetPageUntyped byUrl(String thePageUrl) { 1156 if (isBlank(thePageUrl)) { 1157 throw new IllegalArgumentException("thePagingUrl must not be blank or null"); 1158 } 1159 myPageUrl = thePageUrl; 1160 return this; 1161 } 1162 1163 @Override 1164 public IGetPageTyped next(Bundle theBundle) { 1165 return new GetPageInternal(theBundle.getLinkNext().getValue()); 1166 } 1167 1168 @Override 1169 public <T extends IBaseBundle> IGetPageTyped<T> next(T theBundle) { 1170 return nextOrPrevious("next", theBundle); 1171 } 1172 1173 private <T extends IBaseBundle> IGetPageTyped<T> nextOrPrevious(String theWantRel, T theBundle) { 1174 RuntimeResourceDefinition def = myContext.getResourceDefinition(theBundle); 1175 List<IBase> links = def.getChildByName("link").getAccessor().getValues(theBundle); 1176 if (links == null || links.isEmpty()) { 1177 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(GenericClient.class, "noPagingLinkFoundInBundle", theWantRel)); 1178 } 1179 for (IBase nextLink : links) { 1180 BaseRuntimeElementCompositeDefinition linkDef = (BaseRuntimeElementCompositeDefinition) myContext.getElementDefinition(nextLink.getClass()); 1181 List<IBase> rel = linkDef.getChildByName("relation").getAccessor().getValues(nextLink); 1182 if (rel == null || rel.isEmpty()) { 1183 continue; 1184 } 1185 String relation = ((IPrimitiveType<?>) rel.get(0)).getValueAsString(); 1186 if (theWantRel.equals(relation) || (theWantRel == PREVIOUS && PREV.equals(relation))) { 1187 List<IBase> urls = linkDef.getChildByName("url").getAccessor().getValues(nextLink); 1188 if (urls == null || urls.isEmpty()) { 1189 continue; 1190 } 1191 String url = ((IPrimitiveType<?>) urls.get(0)).getValueAsString(); 1192 if (isBlank(url)) { 1193 continue; 1194 } 1195 return (IGetPageTyped<T>) byUrl(url).andReturnBundle(theBundle.getClass()); 1196 } 1197 } 1198 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(GenericClient.class, "noPagingLinkFoundInBundle", theWantRel)); 1199 } 1200 1201 @Override 1202 public IGetPageTyped previous(Bundle theBundle) { 1203 return new GetPageInternal(theBundle.getLinkPrevious().getValue()); 1204 } 1205 1206 @Override 1207 public <T extends IBaseBundle> IGetPageTyped<T> previous(T theBundle) { 1208 return nextOrPrevious(PREVIOUS, theBundle); 1209 } 1210 1211 @Override 1212 public IGetPageTyped url(String thePageUrl) { 1213 return new GetPageInternal(thePageUrl); 1214 } 1215 1216 } 1217 1218 @SuppressWarnings("rawtypes") 1219 private class MetaInternal extends BaseClientExecutable implements IMeta, IMetaAddOrDeleteUnsourced, IMetaGetUnsourced, IMetaAddOrDeleteSourced { 1220 1221 private IIdType myId; 1222 private IBaseMetaType myMeta; 1223 private Class<? extends IBaseMetaType> myMetaType; 1224 private String myOnType; 1225 private MetaOperation myOperation; 1226 1227 @Override 1228 public IMetaAddOrDeleteUnsourced add() { 1229 myOperation = MetaOperation.ADD; 1230 return this; 1231 } 1232 1233 @Override 1234 public IMetaAddOrDeleteUnsourced delete() { 1235 myOperation = MetaOperation.DELETE; 1236 return this; 1237 } 1238 1239 @SuppressWarnings("unchecked") 1240 @Override 1241 public Object execute() { 1242 1243 BaseHttpClientInvocation invocation = null; 1244 1245 IBaseParameters parameters = ParametersUtil.newInstance(myContext); 1246 switch (myOperation) { 1247 case ADD: 1248 ParametersUtil.addParameterToParameters(myContext, parameters, myMeta, "meta"); 1249 invocation = OperationMethodBinding.createOperationInvocation(myContext, myId.getResourceType(), myId.getIdPart(), "$meta-add", parameters, false); 1250 break; 1251 case DELETE: 1252 ParametersUtil.addParameterToParameters(myContext, parameters, myMeta, "meta"); 1253 invocation = OperationMethodBinding.createOperationInvocation(myContext, myId.getResourceType(), myId.getIdPart(), "$meta-delete", parameters, false); 1254 break; 1255 case GET: 1256 if (myId != null) { 1257 invocation = OperationMethodBinding.createOperationInvocation(myContext, myOnType, myId.getIdPart(), "$meta", parameters, true); 1258 } else if (myOnType != null) { 1259 invocation = OperationMethodBinding.createOperationInvocation(myContext, myOnType, null, "$meta", parameters, true); 1260 } else { 1261 invocation = OperationMethodBinding.createOperationInvocation(myContext, null, null, "$meta", parameters, true); 1262 } 1263 break; 1264 } 1265 1266 // Should not happen 1267 if (invocation == null) { 1268 throw new IllegalStateException(); 1269 } 1270 1271 IClientResponseHandler handler; 1272 handler = new MetaParametersResponseHandler(myMetaType); 1273 return invoke(null, handler, invocation); 1274 } 1275 1276 @Override 1277 public IClientExecutable fromResource(IIdType theId) { 1278 setIdInternal(theId); 1279 return this; 1280 } 1281 1282 @Override 1283 public IClientExecutable fromServer() { 1284 return this; 1285 } 1286 1287 @Override 1288 public IClientExecutable fromType(String theResourceName) { 1289 Validate.notBlank(theResourceName, "theResourceName must not be blank"); 1290 myOnType = theResourceName; 1291 return this; 1292 } 1293 1294 @SuppressWarnings("unchecked") 1295 @Override 1296 public <T extends IBaseMetaType> IMetaGetUnsourced<T> get(Class<T> theType) { 1297 myMetaType = theType; 1298 myOperation = MetaOperation.GET; 1299 return this; 1300 } 1301 1302 @SuppressWarnings("unchecked") 1303 @Override 1304 public <T extends IBaseMetaType> IClientExecutable<IClientExecutable<?, ?>, T> meta(T theMeta) { 1305 Validate.notNull(theMeta, "theMeta must not be null"); 1306 myMeta = theMeta; 1307 myMetaType = myMeta.getClass(); 1308 return this; 1309 } 1310 1311 @Override 1312 public IMetaAddOrDeleteSourced onResource(IIdType theId) { 1313 setIdInternal(theId); 1314 return this; 1315 } 1316 1317 private void setIdInternal(IIdType theId) { 1318 Validate.notBlank(theId.getResourceType(), "theId must contain a resource type"); 1319 Validate.notBlank(theId.getIdPart(), "theId must contain an ID part"); 1320 myOnType = theId.getResourceType(); 1321 myId = theId; 1322 } 1323 1324 } 1325 1326 private enum MetaOperation { 1327 ADD, DELETE, GET 1328 } 1329 1330 private final class MetaParametersResponseHandler<T extends IBaseMetaType> implements IClientResponseHandler<T> { 1331 1332 private Class<T> myType; 1333 1334 public MetaParametersResponseHandler(Class<T> theMetaType) { 1335 myType = theMetaType; 1336 } 1337 1338 @SuppressWarnings("unchecked") 1339 @Override 1340 public T invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException { 1341 EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType); 1342 if (respType == null) { 1343 throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader); 1344 } 1345 IParser parser = respType.newParser(myContext); 1346 RuntimeResourceDefinition type = myContext.getResourceDefinition("Parameters"); 1347 IBaseResource retVal = parser.parseResource(type.getImplementingClass(), theResponseReader); 1348 1349 BaseRuntimeChildDefinition paramChild = type.getChildByName("parameter"); 1350 BaseRuntimeElementCompositeDefinition<?> paramChildElem = (BaseRuntimeElementCompositeDefinition<?>) paramChild.getChildByName("parameter"); 1351 List<IBase> parameter = paramChild.getAccessor().getValues(retVal); 1352 if (parameter == null || parameter.isEmpty()) { 1353 return (T) myContext.getElementDefinition(myType).newInstance(); 1354 } 1355 IBase param = parameter.get(0); 1356 1357 List<IBase> meta = paramChildElem.getChildByName("value[x]").getAccessor().getValues(param); 1358 if (meta.isEmpty()) { 1359 return (T) myContext.getElementDefinition(myType).newInstance(); 1360 } 1361 return (T) meta.get(0); 1362 1363 } 1364 } 1365 1366 @SuppressWarnings("rawtypes") 1367 private class OperationInternal extends BaseClientExecutable implements IOperation, IOperationUnnamed, IOperationUntyped, IOperationUntypedWithInput, IOperationUntypedWithInputAndPartialOutput { 1368 1369 private IIdType myId; 1370 private String myOperationName; 1371 private IBaseParameters myParameters; 1372 private Class<? extends IBaseResource> myType; 1373 private boolean myUseHttpGet; 1374 private RuntimeResourceDefinition myParametersDef; 1375 1376 @SuppressWarnings("unchecked") 1377 @Override 1378 public Object execute() { 1379 String resourceName; 1380 String id; 1381 if (myType != null) { 1382 resourceName = myContext.getResourceDefinition(myType).getName(); 1383 id = null; 1384 } else if (myId != null) { 1385 resourceName = myId.getResourceType(); 1386 id = myId.getIdPart(); 1387 } else { 1388 resourceName = null; 1389 id = null; 1390 } 1391 1392 BaseHttpClientInvocation invocation = OperationMethodBinding.createOperationInvocation(myContext, resourceName, id, myOperationName, myParameters, myUseHttpGet); 1393 1394 IClientResponseHandler handler; 1395 handler = new ResourceResponseHandler(null, null); 1396 1397 Object retVal = invoke(null, handler, invocation); 1398 if (myContext.getResourceDefinition((IBaseResource) retVal).getName().equals("Parameters")) { 1399 return retVal; 1400 } else { 1401 RuntimeResourceDefinition def = myContext.getResourceDefinition("Parameters"); 1402 IBaseResource parameters = def.newInstance(); 1403 1404 BaseRuntimeChildDefinition paramChild = def.getChildByName("parameter"); 1405 BaseRuntimeElementCompositeDefinition<?> paramChildElem = (BaseRuntimeElementCompositeDefinition<?>) paramChild.getChildByName("parameter"); 1406 IBase parameter = paramChildElem.newInstance(); 1407 paramChild.getMutator().addValue(parameters, parameter); 1408 1409 BaseRuntimeChildDefinition resourceElem = paramChildElem.getChildByName("resource"); 1410 resourceElem.getMutator().addValue(parameter, (IBase) retVal); 1411 1412 return parameters; 1413 } 1414 } 1415 1416 @Override 1417 public IOperationUntyped named(String theName) { 1418 Validate.notBlank(theName, "theName can not be null"); 1419 myOperationName = theName; 1420 return this; 1421 } 1422 1423 @Override 1424 public IOperationUnnamed onInstance(IIdType theId) { 1425 myId = theId; 1426 return this; 1427 } 1428 1429 @Override 1430 public IOperationUnnamed onServer() { 1431 return this; 1432 } 1433 1434 @Override 1435 public IOperationUnnamed onType(Class<? extends IBaseResource> theResourceType) { 1436 myType = theResourceType; 1437 return this; 1438 } 1439 1440 @Override 1441 public IOperationUntypedWithInput useHttpGet() { 1442 myUseHttpGet = true; 1443 return this; 1444 } 1445 1446 @SuppressWarnings("unchecked") 1447 @Override 1448 public <T extends IBaseParameters> IOperationUntypedWithInput<T> withNoParameters(Class<T> theOutputParameterType) { 1449 Validate.notNull(theOutputParameterType, "theOutputParameterType may not be null"); 1450 RuntimeResourceDefinition def = myContext.getResourceDefinition(theOutputParameterType); 1451 if (def == null) { 1452 throw new IllegalArgumentException("theOutputParameterType must refer to a HAPI FHIR Resource type: " + theOutputParameterType.getName()); 1453 } 1454 if (!"Parameters".equals(def.getName())) { 1455 throw new IllegalArgumentException("theOutputParameterType must refer to a HAPI FHIR Resource type for a resource named " + "Parameters" + " - " + theOutputParameterType.getName() 1456 + " is a resource named: " + def.getName()); 1457 } 1458 myParameters = (IBaseParameters) def.newInstance(); 1459 return this; 1460 } 1461 1462 @SuppressWarnings({ "unchecked" }) 1463 @Override 1464 public IOperationUntypedWithInput withParameters(IBaseParameters theParameters) { 1465 Validate.notNull(theParameters, "theParameters can not be null"); 1466 myParameters = theParameters; 1467 return this; 1468 } 1469 1470 @SuppressWarnings("unchecked") 1471 @Override 1472 public <T extends IBaseParameters> IOperationUntypedWithInputAndPartialOutput<T> withParameter(Class<T> theParameterType, String theName, IBase theValue) { 1473 Validate.notNull(theParameterType, "theParameterType must not be null"); 1474 Validate.notEmpty(theName, "theName must not be null"); 1475 Validate.notNull(theValue, "theValue must not be null"); 1476 1477 myParametersDef = myContext.getResourceDefinition(theParameterType); 1478 myParameters = (IBaseParameters) myParametersDef.newInstance(); 1479 1480 addParam(theName, theValue); 1481 1482 return this; 1483 } 1484 1485 @SuppressWarnings("unchecked") 1486 private void addParam(String theName, IBase theValue) { 1487 BaseRuntimeChildDefinition parameterChild = myParametersDef.getChildByName("parameter"); 1488 BaseRuntimeElementCompositeDefinition<?> parameterElem = (BaseRuntimeElementCompositeDefinition<?>) parameterChild.getChildByName("parameter"); 1489 1490 IBase parameter = parameterElem.newInstance(); 1491 parameterChild.getMutator().addValue(myParameters, parameter); 1492 1493 IPrimitiveType<String> name = (IPrimitiveType<String>) myContext.getElementDefinition("string").newInstance(); 1494 name.setValue(theName); 1495 parameterElem.getChildByName("name").getMutator().setValue(parameter, name); 1496 1497 if (theValue instanceof IBaseDatatype) { 1498 BaseRuntimeElementDefinition<?> datatypeDef = myContext.getElementDefinition(theValue.getClass()); 1499 if (datatypeDef instanceof IRuntimeDatatypeDefinition) { 1500 Class<? extends IBaseDatatype> profileOf = ((IRuntimeDatatypeDefinition) datatypeDef).getProfileOf(); 1501 if (profileOf != null) { 1502 datatypeDef = myContext.getElementDefinition(profileOf); 1503 } 1504 } 1505 String childElementName = "value" + StringUtils.capitalize(datatypeDef.getName()); 1506 BaseRuntimeChildDefinition childByName = parameterElem.getChildByName(childElementName); 1507 childByName.getMutator().setValue(parameter, theValue); 1508 } else if (theValue instanceof IBaseResource) { 1509 parameterElem.getChildByName("resource").getMutator().setValue(parameter, theValue); 1510 } else { 1511 throw new IllegalArgumentException("Don't know how to handle parameter of type " + theValue.getClass()); 1512 } 1513 } 1514 1515 @Override 1516 public IOperationUntypedWithInputAndPartialOutput andParameter(String theName, IBase theValue) { 1517 Validate.notEmpty(theName, "theName must not be null"); 1518 Validate.notNull(theValue, "theValue must not be null"); 1519 addParam(theName, theValue); 1520 return this; 1521 } 1522 1523 } 1524 1525 private final class OperationOutcomeResponseHandler implements IClientResponseHandler<BaseOperationOutcome> { 1526 1527 @Override 1528 public BaseOperationOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) 1529 throws BaseServerResponseException { 1530 EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType); 1531 if (respType == null) { 1532 return null; 1533 } 1534 IParser parser = respType.newParser(myContext); 1535 BaseOperationOutcome retVal; 1536 try { 1537 // TODO: handle if something else than OO comes back 1538 retVal = (BaseOperationOutcome) parser.parseResource(theResponseReader); 1539 } catch (DataFormatException e) { 1540 ourLog.warn("Failed to parse OperationOutcome response", e); 1541 return null; 1542 } 1543 MethodUtil.parseClientRequestResourceHeaders(null, theHeaders, retVal); 1544 1545 return retVal; 1546 } 1547 } 1548 1549 private final class OutcomeResponseHandler implements IClientResponseHandler<MethodOutcome> { 1550 private final String myResourceName; 1551 1552 private OutcomeResponseHandler(String theResourceName) { 1553 myResourceName = theResourceName; 1554 } 1555 1556 @Override 1557 public MethodOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException { 1558 MethodOutcome response = MethodUtil.process2xxResponse(myContext, myResourceName, theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders); 1559 if (theResponseStatusCode == Constants.STATUS_HTTP_201_CREATED) { 1560 response.setCreated(true); 1561 } 1562 return response; 1563 } 1564 } 1565 1566 @SuppressWarnings({ "rawtypes", "unchecked" }) 1567 private class ReadInternal extends BaseClientExecutable implements IRead, IReadTyped, IReadExecutable { 1568 private IIdType myId; 1569 private String myIfVersionMatches; 1570 private ICallable myNotModifiedHandler; 1571 private RuntimeResourceDefinition myType; 1572 1573 @Override 1574 public Object execute() {// AAA 1575 if (myId.hasVersionIdPart()) { 1576 return doReadOrVRead(myType.getImplementingClass(), myId, true, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements()); 1577 } else { 1578 return doReadOrVRead(myType.getImplementingClass(), myId, false, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements()); 1579 } 1580 } 1581 1582 @Override 1583 public IReadIfNoneMatch ifVersionMatches(String theVersion) { 1584 myIfVersionMatches = theVersion; 1585 return new IReadIfNoneMatch() { 1586 1587 @Override 1588 public IReadExecutable returnNull() { 1589 myNotModifiedHandler = new ICallable() { 1590 @Override 1591 public Object call() { 1592 return null; 1593 } 1594 }; 1595 return ReadInternal.this; 1596 } 1597 1598 @Override 1599 public IReadExecutable returnResource(final IBaseResource theInstance) { 1600 myNotModifiedHandler = new ICallable() { 1601 @Override 1602 public Object call() { 1603 return theInstance; 1604 } 1605 }; 1606 return ReadInternal.this; 1607 } 1608 1609 @Override 1610 public IReadExecutable throwNotModifiedException() { 1611 myNotModifiedHandler = null; 1612 return ReadInternal.this; 1613 } 1614 }; 1615 } 1616 1617 private void processUrl() { 1618 String resourceType = myId.getResourceType(); 1619 if (isBlank(resourceType)) { 1620 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_INCOMPLETE_URI_FOR_READ, myId)); 1621 } 1622 myType = myContext.getResourceDefinition(resourceType); 1623 if (myType == null) { 1624 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, myId)); 1625 } 1626 } 1627 1628 @Override 1629 public <T extends IBaseResource> IReadTyped<T> resource(Class<T> theResourceType) { 1630 Validate.notNull(theResourceType, "theResourceType must not be null"); 1631 myType = myContext.getResourceDefinition(theResourceType); 1632 if (myType == null) { 1633 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theResourceType)); 1634 } 1635 return this; 1636 } 1637 1638 @Override 1639 public IReadTyped<IBaseResource> resource(String theResourceAsText) { 1640 Validate.notBlank(theResourceAsText, "You must supply a value for theResourceAsText"); 1641 myType = myContext.getResourceDefinition(theResourceAsText); 1642 if (myType == null) { 1643 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theResourceAsText)); 1644 } 1645 return this; 1646 } 1647 1648 @Override 1649 public IReadExecutable withId(IIdType theId) { 1650 Validate.notNull(theId, "The ID can not be null"); 1651 Validate.notBlank(theId.getIdPart(), "The ID can not be blank"); 1652 myId = theId.toUnqualified(); 1653 return this; 1654 } 1655 1656 @Override 1657 public IReadExecutable withId(Long theId) { 1658 Validate.notNull(theId, "The ID can not be null"); 1659 myId = new IdDt(myType.getName(), theId); 1660 return this; 1661 } 1662 1663 @Override 1664 public IReadExecutable withId(String theId) { 1665 Validate.notBlank(theId, "The ID can not be blank"); 1666 myId = new IdDt(myType.getName(), theId); 1667 return this; 1668 } 1669 1670 @Override 1671 public IReadExecutable withIdAndVersion(String theId, String theVersion) { 1672 Validate.notBlank(theId, "The ID can not be blank"); 1673 myId = new IdDt(myType.getName(), theId, theVersion); 1674 return this; 1675 } 1676 1677 @Override 1678 public IReadExecutable withUrl(IIdType theUrl) { 1679 Validate.notNull(theUrl, "theUrl can not be null"); 1680 myId = theUrl; 1681 processUrl(); 1682 return this; 1683 } 1684 1685 @Override 1686 public IReadExecutable withUrl(String theUrl) { 1687 myId = new IdDt(theUrl); 1688 processUrl(); 1689 return this; 1690 } 1691 1692 } 1693 1694 private final class ResourceListResponseHandler implements IClientResponseHandler<List<IBaseResource>> { 1695 1696 private Class<? extends IBaseResource> myType; 1697 1698 public ResourceListResponseHandler(Class<? extends IBaseResource> theType) { 1699 myType = theType; 1700 } 1701 1702 @SuppressWarnings("unchecked") 1703 @Override 1704 public List<IBaseResource> invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) 1705 throws BaseServerResponseException { 1706 if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { 1707 Class<? extends IBaseResource> bundleType = myContext.getResourceDefinition("Bundle").getImplementingClass(); 1708 ResourceResponseHandler<IBaseResource> handler = new ResourceResponseHandler<IBaseResource>((Class<IBaseResource>) bundleType, null); 1709 IBaseResource response = handler.invokeClient(theResponseMimeType, theResponseReader, theResponseStatusCode, theHeaders); 1710 IVersionSpecificBundleFactory bundleFactory = myContext.newBundleFactory(); 1711 bundleFactory.initializeWithBundleResource(response); 1712 return bundleFactory.toListOfResources(); 1713 } else { 1714 return new ArrayList<IBaseResource>(new BundleResponseHandler(myType).invokeClient(theResponseMimeType, theResponseReader, theResponseStatusCode, theHeaders).toListOfResources()); 1715 } 1716 } 1717 } 1718 1719 @SuppressWarnings({ "rawtypes", "unchecked" }) 1720 private class SearchInternal extends BaseClientExecutable<IQuery<Object>, Object>implements IQuery<Object>, IUntypedQuery { 1721 1722 private String myCompartmentName; 1723 private CriterionList myCriterion = new CriterionList(); 1724 private List<Include> myInclude = new ArrayList<Include>(); 1725 private DateRangeParam myLastUpdated; 1726 private Integer myParamLimit; 1727 private List<String> myProfile = new ArrayList<String>(); 1728 private String myResourceId; 1729 private String myResourceName; 1730 private Class<? extends IBaseResource> myResourceType; 1731 private Class<? extends IBaseBundle> myReturnBundleType; 1732 private List<Include> myRevInclude = new ArrayList<Include>(); 1733 private SearchStyleEnum mySearchStyle; 1734 private List<TokenParam> mySecurity = new ArrayList<TokenParam>(); 1735 private List<SortInternal> mySort = new ArrayList<SortInternal>(); 1736 private List<TokenParam> myTags = new ArrayList<TokenParam>(); 1737 private String mySearchUrl; 1738 1739 public SearchInternal() { 1740 myResourceType = null; 1741 myResourceName = null; 1742 mySearchUrl = null; 1743 } 1744 1745 @Override 1746 public IQuery and(ICriterion<?> theCriterion) { 1747 myCriterion.add((ICriterionInternal) theCriterion); 1748 return this; 1749 } 1750 1751 @Override 1752 public IBase execute() { 1753 1754 Map<String, List<String>> params = new LinkedHashMap<String, List<String>>(); 1755 // Map<String, List<String>> initial = createExtraParams(); 1756 // if (initial != null) { 1757 // params.putAll(initial); 1758 // } 1759 1760 myCriterion.populateParamList(params); 1761 1762 for (TokenParam next : myTags) { 1763 addParam(params, Constants.PARAM_TAG, next.getValueAsQueryToken()); 1764 } 1765 1766 for (TokenParam next : mySecurity) { 1767 addParam(params, Constants.PARAM_SECURITY, next.getValueAsQueryToken()); 1768 } 1769 1770 for (String next : myProfile) { 1771 addParam(params, Constants.PARAM_PROFILE, next); 1772 } 1773 1774 for (Include next : myInclude) { 1775 if (next.isRecurse()) { 1776 addParam(params, Constants.PARAM_INCLUDE_RECURSE, next.getValue()); 1777 } else { 1778 addParam(params, Constants.PARAM_INCLUDE, next.getValue()); 1779 } 1780 } 1781 1782 for (Include next : myRevInclude) { 1783 addParam(params, Constants.PARAM_REVINCLUDE, next.getValue()); 1784 } 1785 1786 for (SortInternal next : mySort) { 1787 addParam(params, next.getParamName(), next.getParamValue()); 1788 } 1789 1790 if (myParamLimit != null) { 1791 addParam(params, Constants.PARAM_COUNT, Integer.toString(myParamLimit)); 1792 } 1793 1794 if (myLastUpdated != null) { 1795 for (DateParam next : myLastUpdated.getValuesAsQueryTokens()) { 1796 addParam(params, Constants.PARAM_LASTUPDATED, next.getValueAsQueryToken()); 1797 } 1798 } 1799 1800 if (myReturnBundleType == null && myContext.getVersion().getVersion().isRi()) { 1801 throw new IllegalArgumentException("When using the client with HL7.org structures, you must specify " 1802 + "the bundle return type for the client by adding \".returnBundle(org.hl7.fhir.instance.model.Bundle.class)\" to your search method call before the \".execute()\" method"); 1803 } 1804 1805 IClientResponseHandler<? extends IBase> binding; 1806 if (myReturnBundleType != null) { 1807 binding = new ResourceResponseHandler(myReturnBundleType, null); 1808 } else { 1809 binding = new BundleResponseHandler(myResourceType); 1810 } 1811 1812 IdDt resourceId = myResourceId != null ? new IdDt(myResourceId) : null; 1813 1814 BaseHttpClientInvocation invocation; 1815 if (mySearchUrl != null) { 1816 invocation = SearchMethodBinding.createSearchInvocation(mySearchUrl, params); 1817 } else { 1818 invocation = SearchMethodBinding.createSearchInvocation(myContext, myResourceName, params, resourceId, myCompartmentName, mySearchStyle); 1819 } 1820 1821 return invoke(params, binding, invocation); 1822 1823 } 1824 1825 @Override 1826 public IQuery forAllResources() { 1827 return this; 1828 } 1829 1830 @Override 1831 public IQuery forResource(Class<? extends IBaseResource> theResourceType) { 1832 setType(theResourceType); 1833 return this; 1834 } 1835 1836 @Override 1837 public IQuery forResource(String theResourceName) { 1838 setType(theResourceName); 1839 return this; 1840 } 1841 1842 @Override 1843 public IQuery include(Include theInclude) { 1844 myInclude.add(theInclude); 1845 return this; 1846 } 1847 1848 @Override 1849 public IQuery lastUpdated(DateRangeParam theLastUpdated) { 1850 myLastUpdated = theLastUpdated; 1851 return this; 1852 } 1853 1854 @Override 1855 public IQuery limitTo(int theLimitTo) { 1856 return count(theLimitTo); 1857 } 1858 1859 @Override 1860 public IQuery count(int theLimitTo) { 1861 if (theLimitTo > 0) { 1862 myParamLimit = theLimitTo; 1863 } else { 1864 myParamLimit = null; 1865 } 1866 return this; 1867 } 1868 1869 @Override 1870 public IQuery returnBundle(Class theClass) { 1871 if (theClass == null) { 1872 throw new NullPointerException("theClass must not be null"); 1873 } 1874 myReturnBundleType = theClass; 1875 return this; 1876 } 1877 1878 @Override 1879 public IQuery revInclude(Include theInclude) { 1880 myRevInclude.add(theInclude); 1881 return this; 1882 } 1883 1884 private void setType(Class<? extends IBaseResource> theResourceType) { 1885 myResourceType = theResourceType; 1886 RuntimeResourceDefinition definition = myContext.getResourceDefinition(theResourceType); 1887 myResourceName = definition.getName(); 1888 } 1889 1890 private void setType(String theResourceName) { 1891 myResourceType = myContext.getResourceDefinition(theResourceName).getImplementingClass(); 1892 myResourceName = theResourceName; 1893 } 1894 1895 @Override 1896 public ISort sort() { 1897 SortInternal retVal = new SortInternal(this); 1898 mySort.add(retVal); 1899 return retVal; 1900 } 1901 1902 @Override 1903 public IQuery usingStyle(SearchStyleEnum theStyle) { 1904 mySearchStyle = theStyle; 1905 return this; 1906 } 1907 1908 @Override 1909 public IQuery where(ICriterion<?> theCriterion) { 1910 myCriterion.add((ICriterionInternal) theCriterion); 1911 return this; 1912 } 1913 1914 @Override 1915 public IQuery withIdAndCompartment(String theResourceId, String theCompartmentName) { 1916 myResourceId = theResourceId; 1917 myCompartmentName = theCompartmentName; 1918 return this; 1919 } 1920 1921 @Override 1922 public IQuery<Object> withProfile(String theProfileUri) { 1923 Validate.notBlank(theProfileUri, "theProfileUri must not be null or empty"); 1924 myProfile.add(theProfileUri); 1925 return this; 1926 } 1927 1928 @Override 1929 public IQuery<Object> withSecurity(String theSystem, String theCode) { 1930 Validate.notBlank(theCode, "theCode must not be null or empty"); 1931 mySecurity.add(new TokenParam(theSystem, theCode)); 1932 return this; 1933 } 1934 1935 @Override 1936 public IQuery<Object> withTag(String theSystem, String theCode) { 1937 Validate.notBlank(theCode, "theCode must not be null or empty"); 1938 myTags.add(new TokenParam(theSystem, theCode)); 1939 return this; 1940 } 1941 1942 @Override 1943 public IQuery byUrl(String theSearchUrl) { 1944 Validate.notBlank(theSearchUrl, "theSearchUrl must not be blank/null"); 1945 1946 if (theSearchUrl.startsWith("http://") || theSearchUrl.startsWith("https://")) { 1947 mySearchUrl = theSearchUrl; 1948 int qIndex = mySearchUrl.indexOf('?'); 1949 if (qIndex != -1) { 1950 mySearchUrl = mySearchUrl.substring(0, qIndex) + validateAndEscapeConditionalUrl(mySearchUrl.substring(qIndex)); 1951 } 1952 } else { 1953 String searchUrl = theSearchUrl; 1954 if (searchUrl.startsWith("/")) { 1955 searchUrl = searchUrl.substring(1); 1956 } 1957 if (!searchUrl.matches("[a-zA-Z]+($|\\?.*)")) { 1958 throw new IllegalArgumentException("Search URL must be either a complete URL starting with http: or https:, or a relative FHIR URL in the form [ResourceType]?[Params]"); 1959 } 1960 int qIndex = searchUrl.indexOf('?'); 1961 if (qIndex == -1) { 1962 mySearchUrl = getUrlBase() + '/' + searchUrl; 1963 } else { 1964 mySearchUrl = getUrlBase() + '/' + validateAndEscapeConditionalUrl(searchUrl); 1965 } 1966 } 1967 return this; 1968 } 1969 1970 } 1971 1972 @SuppressWarnings("rawtypes") 1973 private static class SortInternal implements ISort { 1974 1975 private SearchInternal myFor; 1976 private String myParamName; 1977 private String myParamValue; 1978 1979 public SortInternal(SearchInternal theFor) { 1980 myFor = theFor; 1981 } 1982 1983 @Override 1984 public IQuery ascending(IParam theParam) { 1985 myParamName = Constants.PARAM_SORT_ASC; 1986 myParamValue = theParam.getParamName(); 1987 return myFor; 1988 } 1989 1990 @Override 1991 public IQuery defaultOrder(IParam theParam) { 1992 myParamName = Constants.PARAM_SORT; 1993 myParamValue = theParam.getParamName(); 1994 return myFor; 1995 } 1996 1997 @Override 1998 public IQuery descending(IParam theParam) { 1999 myParamName = Constants.PARAM_SORT_DESC; 2000 myParamValue = theParam.getParamName(); 2001 return myFor; 2002 } 2003 2004 public String getParamName() { 2005 return myParamName; 2006 } 2007 2008 public String getParamValue() { 2009 return myParamValue; 2010 } 2011 2012 } 2013 2014 private final class StringResponseHandler implements IClientResponseHandler<String> { 2015 2016 @Override 2017 public String invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) 2018 throws IOException, BaseServerResponseException { 2019 return IOUtils.toString(theResponseReader); 2020 } 2021 } 2022 2023 private final class TagListResponseHandler implements IClientResponseHandler<TagList> { 2024 2025 @Override 2026 public TagList invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException { 2027 EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType); 2028 if (respType == null) { 2029 throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader); 2030 } 2031 IParser parser = respType.newParser(myContext); 2032 return parser.parseTagList(theResponseReader); 2033 } 2034 } 2035 2036 private final class TransactionExecutable<T> extends BaseClientExecutable<ITransactionTyped<T>, T>implements ITransactionTyped<T> { 2037 2038 private IBaseBundle myBaseBundle; 2039 private Bundle myBundle; 2040 private String myRawBundle; 2041 private EncodingEnum myRawBundleEncoding; 2042 private List<? extends IBaseResource> myResources; 2043 2044 public TransactionExecutable(Bundle theResources) { 2045 myBundle = theResources; 2046 } 2047 2048 public TransactionExecutable(IBaseBundle theBundle) { 2049 myBaseBundle = theBundle; 2050 } 2051 2052 public TransactionExecutable(List<? extends IBaseResource> theResources) { 2053 myResources = theResources; 2054 } 2055 2056 public TransactionExecutable(String theBundle) { 2057 myRawBundle = theBundle; 2058 myRawBundleEncoding = MethodUtil.detectEncodingNoDefault(myRawBundle); 2059 if (myRawBundleEncoding == null) { 2060 throw new IllegalArgumentException("Can not determine encoding of raw resource body"); 2061 } 2062 } 2063 2064 @SuppressWarnings({ "unchecked", "rawtypes" }) 2065 @Override 2066 public T execute() { 2067 Map<String, List<String>> params = new HashMap<String, List<String>>(); 2068 if (myResources != null) { 2069 ResourceListResponseHandler binding = new ResourceListResponseHandler(null); 2070 BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myResources, myContext); 2071 return (T) invoke(params, binding, invocation); 2072 } else if (myBaseBundle != null) { 2073 ResourceResponseHandler binding = new ResourceResponseHandler(myBaseBundle.getClass(), null); 2074 BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myBaseBundle, myContext); 2075 return (T) invoke(params, binding, invocation); 2076 } else if (myRawBundle != null) { 2077 StringResponseHandler binding = new StringResponseHandler(); 2078 /* 2079 * If the user has explicitly requested a given encoding, we may need to reencode the raw string 2080 */ 2081 if (getParamEncoding() != null) { 2082 if (MethodUtil.detectEncodingNoDefault(myRawBundle) != getParamEncoding()) { 2083 IBaseResource parsed = parseResourceBody(myRawBundle); 2084 myRawBundle = getParamEncoding().newParser(getFhirContext()).encodeResourceToString(parsed); 2085 } 2086 } 2087 BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myRawBundle, myContext); 2088 return (T) invoke(params, binding, invocation); 2089 } else { 2090 BundleResponseHandler binding = new BundleResponseHandler(null); 2091 BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myBundle, myContext); 2092 return (T) invoke(params, binding, invocation); 2093 } 2094 } 2095 2096 } 2097 2098 private final class TransactionInternal implements ITransaction { 2099 2100 @Override 2101 public ITransactionTyped<Bundle> withBundle(Bundle theBundle) { 2102 Validate.notNull(theBundle, "theBundle must not be null"); 2103 return new TransactionExecutable<Bundle>(theBundle); 2104 } 2105 2106 @Override 2107 public ITransactionTyped<String> withBundle(String theBundle) { 2108 Validate.notBlank(theBundle, "theBundle must not be null"); 2109 return new TransactionExecutable<String>(theBundle); 2110 } 2111 2112 @Override 2113 public <T extends IBaseBundle> ITransactionTyped<T> withBundle(T theBundle) { 2114 Validate.notNull(theBundle, "theBundle must not be null"); 2115 return new TransactionExecutable<T>(theBundle); 2116 } 2117 2118 @Override 2119 public ITransactionTyped<List<IBaseResource>> withResources(List<? extends IBaseResource> theResources) { 2120 Validate.notNull(theResources, "theResources must not be null"); 2121 return new TransactionExecutable<List<IBaseResource>>(theResources); 2122 } 2123 2124 } 2125 2126 private class UpdateInternal extends BaseClientExecutable<IUpdateExecutable, MethodOutcome>implements IUpdate, IUpdateTyped, IUpdateExecutable, IUpdateWithQuery, IUpdateWithQueryTyped { 2127 2128 private CriterionList myCriterionList; 2129 private IIdType myId; 2130 private PreferReturnEnum myPrefer; 2131 private IBaseResource myResource; 2132 private String myResourceBody; 2133 private String mySearchUrl; 2134 2135 @Override 2136 public IUpdateWithQueryTyped and(ICriterion<?> theCriterion) { 2137 myCriterionList.add((ICriterionInternal) theCriterion); 2138 return this; 2139 } 2140 2141 @Override 2142 public IUpdateWithQuery conditional() { 2143 myCriterionList = new CriterionList(); 2144 return this; 2145 } 2146 2147 @Override 2148 public IUpdateTyped conditionalByUrl(String theSearchUrl) { 2149 mySearchUrl = validateAndEscapeConditionalUrl(theSearchUrl); 2150 return this; 2151 } 2152 2153 @Override 2154 public MethodOutcome execute() { 2155 if (myResource == null) { 2156 myResource = parseResourceBody(myResourceBody); 2157 } 2158 2159 // If an explicit encoding is chosen, we will re-serialize to ensure the right encoding 2160 if (getParamEncoding() != null) { 2161 myResourceBody = null; 2162 } 2163 2164 BaseHttpClientInvocation invocation; 2165 if (mySearchUrl != null) { 2166 invocation = MethodUtil.createUpdateInvocation(myContext, myResource, myResourceBody, mySearchUrl); 2167 } else if (myCriterionList != null) { 2168 invocation = MethodUtil.createUpdateInvocation(myContext, myResource, myResourceBody, myCriterionList.toParamList()); 2169 } else { 2170 if (myId == null) { 2171 myId = myResource.getIdElement(); 2172 } 2173 if (myId == null || myId.hasIdPart() == false) { 2174 throw new InvalidRequestException("No ID supplied for resource to update, can not invoke server"); 2175 } 2176 invocation = MethodUtil.createUpdateInvocation(myResource, myResourceBody, myId, myContext); 2177 } 2178 2179 addPreferHeader(myPrefer, invocation); 2180 2181 RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource); 2182 final String resourceName = def.getName(); 2183 2184 OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName); 2185 2186 Map<String, List<String>> params = new HashMap<String, List<String>>(); 2187 return invoke(params, binding, invocation); 2188 2189 } 2190 2191 @Override 2192 public IUpdateExecutable prefer(PreferReturnEnum theReturn) { 2193 myPrefer = theReturn; 2194 return this; 2195 } 2196 2197 @Override 2198 public IUpdateTyped resource(IBaseResource theResource) { 2199 Validate.notNull(theResource, "Resource can not be null"); 2200 myResource = theResource; 2201 return this; 2202 } 2203 2204 @Override 2205 public IUpdateTyped resource(String theResourceBody) { 2206 Validate.notBlank(theResourceBody, "Body can not be null or blank"); 2207 myResourceBody = theResourceBody; 2208 return this; 2209 } 2210 2211 @Override 2212 public IUpdateWithQueryTyped where(ICriterion<?> theCriterion) { 2213 myCriterionList.add((ICriterionInternal) theCriterion); 2214 return this; 2215 } 2216 2217 @Override 2218 public IUpdateExecutable withId(IdDt theId) { 2219 if (theId == null) { 2220 throw new NullPointerException("theId can not be null"); 2221 } 2222 if (theId.hasIdPart() == false) { 2223 throw new NullPointerException("theId must not be blank and must contain an ID, found: " + theId.getValue()); 2224 } 2225 myId = theId; 2226 return this; 2227 } 2228 2229 @Override 2230 public IUpdateExecutable withId(String theId) { 2231 if (theId == null) { 2232 throw new NullPointerException("theId can not be null"); 2233 } 2234 if (isBlank(theId)) { 2235 throw new NullPointerException("theId must not be blank and must contain an ID, found: " + theId); 2236 } 2237 myId = new IdDt(theId); 2238 return this; 2239 } 2240 2241 } 2242 2243 private class ValidateInternal extends BaseClientExecutable<IValidateUntyped, MethodOutcome>implements IValidate, IValidateUntyped { 2244 private IBaseResource myResource; 2245 2246 @Override 2247 public MethodOutcome execute() { 2248 BaseHttpClientInvocation invocation = ValidateMethodBindingDstu2.createValidateInvocation(myContext, myResource); 2249 ResourceResponseHandler<BaseOperationOutcome> handler = new ResourceResponseHandler<BaseOperationOutcome>(null, null); 2250 BaseOperationOutcome outcome = invoke(null, handler, invocation); 2251 MethodOutcome retVal = new MethodOutcome(); 2252 retVal.setOperationOutcome(outcome); 2253 return retVal; 2254 } 2255 2256 @Override 2257 public IValidateUntyped resource(IBaseResource theResource) { 2258 Validate.notNull(theResource, "theResource must not be null"); 2259 myResource = theResource; 2260 return this; 2261 } 2262 2263 @Override 2264 public IValidateUntyped resource(String theResourceRaw) { 2265 Validate.notBlank(theResourceRaw, "theResourceRaw must not be null or blank"); 2266 myResource = parseResourceBody(theResourceRaw); 2267 2268 EncodingEnum enc = MethodUtil.detectEncodingNoDefault(theResourceRaw); 2269 if (enc == null) { 2270 throw new IllegalArgumentException("Could not detect encoding (XML/JSON) in string. Is this a valid FHIR resource?"); 2271 } 2272 switch (enc) { 2273 case XML: 2274 encodedXml(); 2275 break; 2276 case JSON: 2277 encodedJson(); 2278 break; 2279 } 2280 return this; 2281 } 2282 2283 } 2284 2285 private static String validateAndEscapeConditionalUrl(String theSearchUrl) { 2286 Validate.notBlank(theSearchUrl, "Conditional URL can not be blank/null"); 2287 StringBuilder b = new StringBuilder(); 2288 boolean haveHadQuestionMark = false; 2289 for (int i = 0; i < theSearchUrl.length(); i++) { 2290 char nextChar = theSearchUrl.charAt(i); 2291 if (!haveHadQuestionMark) { 2292 if (nextChar == '?') { 2293 haveHadQuestionMark = true; 2294 } else if (!Character.isLetter(nextChar)) { 2295 throw new IllegalArgumentException("Conditional URL must be in the format \"[ResourceType]?[Params]\" and must not have a base URL - Found: " + theSearchUrl); 2296 } 2297 b.append(nextChar); 2298 } else { 2299 switch (nextChar) { 2300 case '|': 2301 case '?': 2302 case '$': 2303 case ':': 2304 b.append(UrlUtil.escape(Character.toString(nextChar))); 2305 break; 2306 default: 2307 b.append(nextChar); 2308 break; 2309 } 2310 } 2311 } 2312 return b.toString(); 2313 } 2314 2315}