1 package ca.uhn.fhir.rest.client;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 import static org.apache.commons.lang3.StringUtils.isBlank;
24 import static org.apache.commons.lang3.StringUtils.isNotBlank;
25
26 import java.io.IOException;
27 import java.io.Reader;
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.LinkedHashMap;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Map.Entry;
34
35 import org.apache.commons.lang3.StringUtils;
36 import org.apache.commons.lang3.Validate;
37 import org.apache.http.client.HttpClient;
38 import org.apache.http.client.methods.HttpRequestBase;
39
40 import ca.uhn.fhir.context.FhirContext;
41 import ca.uhn.fhir.context.RuntimeResourceDefinition;
42 import ca.uhn.fhir.model.api.Bundle;
43 import ca.uhn.fhir.model.api.IQueryParameterType;
44 import ca.uhn.fhir.model.api.IResource;
45 import ca.uhn.fhir.model.api.Include;
46 import ca.uhn.fhir.model.api.TagList;
47 import ca.uhn.fhir.model.dstu.resource.Conformance;
48 import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
49 import ca.uhn.fhir.model.primitive.DateTimeDt;
50 import ca.uhn.fhir.model.primitive.IdDt;
51 import ca.uhn.fhir.model.primitive.UriDt;
52 import ca.uhn.fhir.parser.DataFormatException;
53 import ca.uhn.fhir.parser.IParser;
54 import ca.uhn.fhir.rest.api.MethodOutcome;
55 import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
56 import ca.uhn.fhir.rest.gclient.IClientExecutable;
57 import ca.uhn.fhir.rest.gclient.ICreate;
58 import ca.uhn.fhir.rest.gclient.ICreateTyped;
59 import ca.uhn.fhir.rest.gclient.ICriterion;
60 import ca.uhn.fhir.rest.gclient.ICriterionInternal;
61 import ca.uhn.fhir.rest.gclient.IDelete;
62 import ca.uhn.fhir.rest.gclient.IDeleteTyped;
63 import ca.uhn.fhir.rest.gclient.IGetPage;
64 import ca.uhn.fhir.rest.gclient.IGetPageTyped;
65 import ca.uhn.fhir.rest.gclient.IGetTags;
66 import ca.uhn.fhir.rest.gclient.IParam;
67 import ca.uhn.fhir.rest.gclient.IQuery;
68 import ca.uhn.fhir.rest.gclient.ISort;
69 import ca.uhn.fhir.rest.gclient.ITransaction;
70 import ca.uhn.fhir.rest.gclient.ITransactionTyped;
71 import ca.uhn.fhir.rest.gclient.IUntypedQuery;
72 import ca.uhn.fhir.rest.gclient.IUpdate;
73 import ca.uhn.fhir.rest.gclient.IUpdateTyped;
74 import ca.uhn.fhir.rest.method.DeleteMethodBinding;
75 import ca.uhn.fhir.rest.method.HistoryMethodBinding;
76 import ca.uhn.fhir.rest.method.HttpDeleteClientInvocation;
77 import ca.uhn.fhir.rest.method.HttpGetClientInvocation;
78 import ca.uhn.fhir.rest.method.HttpSimpleGetClientInvocation;
79 import ca.uhn.fhir.rest.method.IClientResponseHandler;
80 import ca.uhn.fhir.rest.method.MethodUtil;
81 import ca.uhn.fhir.rest.method.ReadMethodBinding;
82 import ca.uhn.fhir.rest.method.SearchMethodBinding;
83 import ca.uhn.fhir.rest.method.SearchStyleEnum;
84 import ca.uhn.fhir.rest.method.TransactionMethodBinding;
85 import ca.uhn.fhir.rest.method.ValidateMethodBinding;
86 import ca.uhn.fhir.rest.server.Constants;
87 import ca.uhn.fhir.rest.server.EncodingEnum;
88 import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
89 import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
90
91
92
93
94
95 public class GenericClient extends BaseClient implements IGenericClient {
96
97 private static final String I18N_CANNOT_DETEMINE_RESOURCE_TYPE = "ca.uhn.fhir.rest.client.GenericClient.cannotDetermineResourceTypeFromUri";
98 private static final String I18N_INCOMPLETE_URI_FOR_READ = "ca.uhn.fhir.rest.client.GenericClient.incompleteUriForRead";
99 private static final String I18N_NO_VERSION_ID_FOR_VREAD = "ca.uhn.fhir.rest.client.GenericClient.noVersionIdForVread";
100
101 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClient.class);
102
103 private FhirContext myContext;
104
105 private HttpRequestBase myLastRequest;
106
107 private boolean myLogRequestAndResponse;
108
109
110
111
112 public GenericClient(FhirContext theContext, HttpClient theHttpClient, String theServerBase) {
113 super(theHttpClient, theServerBase);
114 myContext = theContext;
115 }
116
117 @Override
118 public Conformance conformance() {
119 HttpGetClientInvocation invocation = MethodUtil.createConformanceInvocation();
120 if (isKeepResponses()) {
121 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
122 }
123
124 ResourceResponseHandler<Conformance> binding = new ResourceResponseHandler<Conformance>(Conformance.class, null);
125 Conformance resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse);
126 return resp;
127 }
128
129 @Override
130 public ICreate create() {
131 return new CreateInternal();
132 }
133
134 @Override
135 public MethodOutcome create(IResource theResource) {
136 BaseHttpClientInvocation invocation = MethodUtil.createCreateInvocation(theResource, myContext);
137 if (isKeepResponses()) {
138 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
139 }
140
141 RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
142 final String resourceName = def.getName();
143
144 OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName);
145
146 MethodOutcome resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse);
147 return resp;
148
149 }
150
151 @Override
152 public IDelete delete() {
153 return new DeleteInternal();
154 }
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169 @Override
170 public MethodOutcome delete(final Class<? extends IResource> theType, IdDt theId) {
171 HttpDeleteClientInvocation invocation = DeleteMethodBinding.createDeleteInvocation(theId.withResourceType(toResourceName(theType)));
172 if (isKeepResponses()) {
173 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
174 }
175
176 final String resourceName = myContext.getResourceDefinition(theType).getName();
177 OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName);
178 MethodOutcome resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse);
179 return resp;
180 }
181
182 @Override
183 public MethodOutcome delete(Class<? extends IResource> theType, String theId) {
184 return delete(theType, new IdDt(theId));
185 }
186
187 public HttpRequestBase getLastRequest() {
188 return myLastRequest;
189 }
190
191 protected String getPreferredId(IResource theResource, String theId) {
192 if (isNotBlank(theId)) {
193 return theId;
194 }
195 return theResource.getId().getIdPart();
196 }
197
198 @Override
199 public IGetTags getTags() {
200 return new GetTagsInternal();
201 }
202
203 @Override
204 public <T extends IResource> Bundle history(final Class<T> theType, IdDt theIdDt, DateTimeDt theSince, Integer theLimit) {
205 String resourceName = theType != null ? toResourceName(theType) : null;
206 IdDt id = theIdDt != null && theIdDt.isEmpty() == false ? theIdDt : null;
207 HttpGetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(resourceName, id, theSince, theLimit);
208 if (isKeepResponses()) {
209 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
210 }
211
212 BundleResponseHandler binding = new BundleResponseHandler(theType);
213 Bundle resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse);
214 return resp;
215
216 }
217
218 @Override
219 public <T extends IResource> Bundle history(Class<T> theType, String theId, DateTimeDt theSince, Integer theLimit) {
220 return history(theType, new IdDt(theId), theSince, theLimit);
221 }
222
223 public boolean isLogRequestAndResponse() {
224 return myLogRequestAndResponse;
225 }
226
227 @Override
228 public IGetPage loadPage() {
229 return new LoadPageInternal();
230 }
231
232 @Override
233 public <T extends IResource> T read(final Class<T> theType, IdDt theId) {
234 if (theId == null || theId.hasIdPart() == false) {
235 throw new IllegalArgumentException("theId does not contain a valid ID, is: " + theId);
236 }
237
238 HttpGetClientInvocation invocation;
239 if (theId.hasBaseUrl()) {
240 invocation = ReadMethodBinding.createAbsoluteReadInvocation(theId.toVersionless());
241 } else {
242 invocation = ReadMethodBinding.createReadInvocation(theId, toResourceName(theType));
243 }
244 if (isKeepResponses()) {
245 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
246 }
247
248 ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theType, theId);
249 T resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse);
250 return resp;
251 }
252
253 @Override
254 public <T extends IResource> T read(Class<T> theType, String theId) {
255 return read(theType, new IdDt(theId));
256 }
257
258 @Override
259 public <T extends IResource> T read(final Class<T> theType, UriDt theUrl) {
260 return read(theType, new IdDt(theUrl));
261 }
262
263 @Override
264 public IResource read(UriDt theUrl) {
265 IdDt id = new IdDt(theUrl);
266 String resourceType = id.getResourceType();
267 if (isBlank(resourceType)) {
268 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_INCOMPLETE_URI_FOR_READ, theUrl.getValueAsString()));
269 }
270 RuntimeResourceDefinition def = myContext.getResourceDefinition(resourceType);
271 if (def == null) {
272 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theUrl.getValueAsString()));
273 }
274 return read(def.getImplementingClass(), id);
275 }
276
277 @Override
278 public IUntypedQuery search() {
279 return new SearchInternal();
280 }
281
282 @Override
283 public <T extends IResource> Bundle search(final Class<T> theType, Map<String, List<IQueryParameterType>> theParams) {
284 LinkedHashMap<String, List<String>> params = new LinkedHashMap<String, List<String>>();
285 for (Entry<String, List<IQueryParameterType>> nextEntry : theParams.entrySet()) {
286 ArrayList<String> valueList = new ArrayList<String>();
287 String qualifier = null;
288 for (IQueryParameterType nextValue : nextEntry.getValue()) {
289 valueList.add(nextValue.getValueAsQueryToken());
290 qualifier = nextValue.getQueryParameterQualifier();
291 }
292 qualifier = StringUtils.defaultString(qualifier);
293 params.put(nextEntry.getKey() + qualifier, valueList);
294 }
295
296 BaseHttpClientInvocation invocation = SearchMethodBinding.createSearchInvocation(myContext, toResourceName(theType), params, null, null, null);
297 if (isKeepResponses()) {
298 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
299 }
300
301 BundleResponseHandler binding = new BundleResponseHandler(theType);
302 Bundle resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse);
303 return resp;
304 }
305
306 @Override
307 public <T extends IResource> Bundle search(final Class<T> theType, UriDt theUrl) {
308 BaseHttpClientInvocation invocation = new HttpGetClientInvocation(theUrl.getValueAsString());
309 return invokeClient(myContext, new BundleResponseHandler(theType), invocation);
310 }
311
312 @Override
313 public Bundle search(UriDt theUrl) {
314 return search(inferResourceClass(theUrl), theUrl);
315 }
316
317 private Class<? extends IResource> inferResourceClass(UriDt theUrl) {
318 String urlString = theUrl.getValueAsString();
319 int i = urlString.indexOf('?');
320
321 if (i >= 0) {
322 urlString = urlString.substring(0, i);
323 }
324
325 i = urlString.indexOf("://");
326
327 if (i >= 0) {
328 urlString = urlString.substring(i + 3);
329 }
330
331 String[] pcs = urlString.split("\\/");
332
333 for (i = pcs.length - 1; i >= 0; i--) {
334 String s = pcs[i].trim();
335
336 if (!s.isEmpty()) {
337 RuntimeResourceDefinition def = myContext.getResourceDefinition(s);
338 if (def != null) {
339 return def.getImplementingClass();
340 }
341 }
342 }
343
344 throw new RuntimeException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theUrl.getValueAsString()));
345
346 }
347
348
349
350
351 public void setLastRequest(HttpRequestBase theLastRequest) {
352 myLastRequest = theLastRequest;
353 }
354
355 @Override
356 public void setLogRequestAndResponse(boolean theLogRequestAndResponse) {
357 myLogRequestAndResponse = theLogRequestAndResponse;
358 }
359
360 private String toResourceName(Class<? extends IResource> theType) {
361 return myContext.getResourceDefinition(theType).getName();
362 }
363
364 @Override
365 public ITransaction transaction() {
366 return new TransactionInternal();
367 }
368
369 @Override
370 public List<IResource> transaction(List<IResource> theResources) {
371 BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(theResources, myContext);
372 if (isKeepResponses()) {
373 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
374 }
375
376 Bundle resp = invokeClient(myContext, new BundleResponseHandler(null), invocation, myLogRequestAndResponse);
377
378 return resp.toListOfResources();
379 }
380
381 @Override
382 public IUpdate update() {
383 return new UpdateInternal();
384 }
385
386 @Override
387 public MethodOutcome update(IdDt theIdDt, IResource theResource) {
388 BaseHttpClientInvocation invocation = MethodUtil.createUpdateInvocation(theResource, null, theIdDt, myContext);
389 if (isKeepResponses()) {
390 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
391 }
392
393 RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
394 final String resourceName = def.getName();
395
396 OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName);
397 MethodOutcome resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse);
398 return resp;
399 }
400
401 @Override
402 public MethodOutcome update(String theId, IResource theResource) {
403 return update(new IdDt(theId), theResource);
404 }
405
406 @Override
407 public MethodOutcome validate(IResource theResource) {
408 BaseHttpClientInvocation invocation = ValidateMethodBinding.createValidateInvocation(theResource, null, myContext);
409 if (isKeepResponses()) {
410 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
411 }
412
413 RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
414 final String resourceName = def.getName();
415
416 OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName);
417 MethodOutcome resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse);
418 return resp;
419 }
420
421 @Override
422 public <T extends IResource> T vread(final Class<T> theType, IdDt theId) {
423 String resName = toResourceName(theType);
424 IdDt id = theId;
425 if (!id.hasBaseUrl()) {
426 id = new IdDt(resName, id.getIdPart(), id.getVersionIdPart());
427 }
428
429 if (id.hasVersionIdPart() == false) {
430 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_NO_VERSION_ID_FOR_VREAD, theId.getValue()));
431 }
432
433 HttpGetClientInvocation invocation;
434 if (id.hasBaseUrl()) {
435 invocation = ReadMethodBinding.createAbsoluteVReadInvocation(id);
436 } else {
437 invocation = ReadMethodBinding.createVReadInvocation(id);
438 }
439 if (isKeepResponses()) {
440 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
441 }
442
443 ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theType, id);
444 T resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse);
445 return resp;
446 }
447
448
449 @Deprecated
450 @Override
451 public <T extends IResource> T vread(final Class<T> theType, IdDt theId, IdDt theVersionId) {
452 return vread(theType, theId.withVersion(theVersionId.getIdPart()));
453 }
454
455 @Override
456 public <T extends IResource> T vread(Class<T> theType, String theId, String theVersionId) {
457 IdDt resId = new IdDt(toResourceName(theType), theId, theVersionId);
458 return vread(theType, resId);
459 }
460
461 private abstract class BaseClientExecutable<T extends IClientExecutable<?, ?>, Y> implements IClientExecutable<T, Y> {
462 private EncodingEnum myParamEncoding;
463 private Boolean myPrettyPrint;
464 private boolean myQueryLogRequestAndResponse;
465
466 protected void addParam(Map<String, List<String>> params, String parameterName, String parameterValue) {
467 if (!params.containsKey(parameterName)) {
468 params.put(parameterName, new ArrayList<String>());
469 }
470 params.get(parameterName).add(parameterValue);
471 }
472
473 @SuppressWarnings("unchecked")
474 @Override
475 public T andLogRequestAndResponse(boolean theLogRequestAndResponse) {
476 myQueryLogRequestAndResponse = theLogRequestAndResponse;
477 return (T) this;
478 }
479
480 @SuppressWarnings("unchecked")
481 @Override
482 public T encodedJson() {
483 myParamEncoding = EncodingEnum.JSON;
484 return (T) this;
485 }
486
487 @SuppressWarnings("unchecked")
488 @Override
489 public T encodedXml() {
490 myParamEncoding = EncodingEnum.XML;
491 return (T) this;
492 }
493
494 protected <Z> Z invoke(Map<String, List<String>> theParams, IClientResponseHandler<Z> theHandler, BaseHttpClientInvocation theInvocation) {
495
496
497
498
499
500
501
502
503 if (isKeepResponses()) {
504 myLastRequest = theInvocation.asHttpRequest(getServerBase(), theParams, getEncoding());
505 }
506
507 Z resp = invokeClient(myContext, theHandler, theInvocation, myParamEncoding, myPrettyPrint, myQueryLogRequestAndResponse || myLogRequestAndResponse);
508 return resp;
509 }
510
511 protected IResource parseResourceBody(String theResourceBody) {
512 EncodingEnum encoding = null;
513 for (int i = 0; i < theResourceBody.length() && encoding == null; i++) {
514 switch (theResourceBody.charAt(i)) {
515 case '<':
516 encoding = EncodingEnum.XML;
517 break;
518 case '{':
519 encoding = EncodingEnum.JSON;
520 break;
521 }
522 }
523 if (encoding == null) {
524 throw new InvalidRequestException("FHIR client can't determine resource encoding");
525 }
526 return encoding.newParser(myContext).parseResource(theResourceBody);
527 }
528
529 @SuppressWarnings("unchecked")
530 @Override
531 public T prettyPrint() {
532 myPrettyPrint = true;
533 return (T) this;
534 }
535
536 }
537
538 private final class BundleResponseHandler implements IClientResponseHandler<Bundle> {
539
540 private Class<? extends IResource> myType;
541
542 public BundleResponseHandler(Class<? extends IResource> theType) {
543 myType = theType;
544 }
545
546 @Override
547 public Bundle invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
548 BaseServerResponseException {
549 EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
550 if (respType == null) {
551 throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
552 }
553 IParser parser = respType.newParser(myContext);
554 return parser.parseBundle(myType, theResponseReader);
555 }
556 }
557
558 private class CreateInternal extends BaseClientExecutable<ICreateTyped, MethodOutcome> implements ICreate, ICreateTyped {
559
560 private String myId;
561 private IResource myResource;
562 private String myResourceBody;
563
564 @Override
565 public MethodOutcome execute() {
566 if (myResource == null) {
567 myResource = parseResourceBody(myResourceBody);
568 }
569 myId = getPreferredId(myResource, myId);
570
571 BaseHttpClientInvocation invocation = MethodUtil.createCreateInvocation(myResource, myResourceBody, myId, myContext);
572
573 RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource);
574 final String resourceName = def.getName();
575
576 OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName);
577
578 Map<String, List<String>> params = new HashMap<String, List<String>>();
579 return invoke(params, binding, invocation);
580
581 }
582
583 @Override
584 public ICreateTyped resource(IResource theResource) {
585 Validate.notNull(theResource, "Resource can not be null");
586 myResource = theResource;
587 return this;
588 }
589
590 @Override
591 public ICreateTyped resource(String theResourceBody) {
592 Validate.notBlank(theResourceBody, "Body can not be null or blank");
593 myResourceBody = theResourceBody;
594 return this;
595 }
596
597 @Override
598 public CreateInternal withId(IdDt theId) {
599 myId = theId.getIdPart();
600 return this;
601 }
602
603 @Override
604 public CreateInternal withId(String theId) {
605 myId = theId;
606 return this;
607 }
608
609 }
610
611 private class DeleteInternal extends BaseClientExecutable<IDeleteTyped, OperationOutcome> implements IDelete, IDeleteTyped {
612
613 private IdDt myId;
614
615 @Override
616 public OperationOutcome execute() {
617 HttpDeleteClientInvocation invocation = DeleteMethodBinding.createDeleteInvocation(myId);
618 OperationOutcomeResponseHandler binding = new OperationOutcomeResponseHandler();
619 Map<String, List<String>> params = new HashMap<String, List<String>>();
620 return invoke(params, binding, invocation);
621 }
622
623 @Override
624 public IDeleteTyped resource(IResource theResource) {
625 Validate.notNull(theResource, "theResource can not be null");
626 IdDt id = theResource.getId();
627 Validate.notNull(id, "theResource.getId() can not be null");
628 if (id.hasResourceType() == false || id.hasIdPart() == false) {
629 throw new IllegalArgumentException("theResource.getId() must contain a resource type and logical ID at a minimum (e.g. Patient/1234), found: " + id.getValue());
630 }
631 myId = id;
632 return this;
633 }
634
635 @Override
636 public IDeleteTyped resourceById(IdDt theId) {
637 Validate.notNull(theId, "theId can not be null");
638 if (theId.hasResourceType() == false || theId.hasIdPart() == false) {
639 throw new IllegalArgumentException("theId must contain a resource type and logical ID at a minimum (e.g. Patient/1234)found: " + theId.getValue());
640 }
641 myId = theId;
642 return this;
643 }
644
645 @Override
646 public IDeleteTyped resourceById(String theResourceType, String theLogicalId) {
647 Validate.notBlank(theResourceType, "theResourceType can not be blank/null");
648 if (myContext.getResourceDefinition(theResourceType) == null) {
649 throw new IllegalArgumentException("Unknown resource type");
650 }
651 Validate.notBlank(theLogicalId, "theLogicalId can not be blank/null");
652 if (theLogicalId.contains("/")) {
653 throw new IllegalArgumentException("LogicalId can not contain '/' (should only be the logical ID portion, not a qualified ID)");
654 }
655 myId = new IdDt(theResourceType, theLogicalId);
656 return this;
657 }
658 }
659
660 private class GetPageInternal extends BaseClientExecutable<IGetPageTyped, Bundle> implements IGetPageTyped {
661
662 private String myUrl;
663
664 public GetPageInternal(String theUrl) {
665 myUrl = theUrl;
666 }
667
668 @Override
669 public Bundle execute() {
670
671 BundleResponseHandler binding = new BundleResponseHandler(null);
672 HttpSimpleGetClientInvocation invocation = new HttpSimpleGetClientInvocation(myUrl);
673
674 Map<String, List<String>> params = null;
675 return invoke(params, binding, invocation);
676
677 }
678
679 }
680
681 private class GetTagsInternal extends BaseClientExecutable<IGetTags, TagList> implements IGetTags {
682
683 private String myId;
684 private String myResourceName;
685 private String myVersionId;
686
687 @Override
688 public TagList execute() {
689
690 Map<String, List<String>> params = new LinkedHashMap<String, List<String>>();
691 Map<String, List<String>> initial = createExtraParams();
692 if (initial != null) {
693 params.putAll(initial);
694 }
695
696 TagListResponseHandler binding = new TagListResponseHandler();
697 List<String> urlFragments = new ArrayList<String>();
698 if (isNotBlank(myResourceName)) {
699 urlFragments.add(myResourceName);
700 if (isNotBlank(myId)) {
701 urlFragments.add(myId);
702 if (isNotBlank(myVersionId)) {
703 urlFragments.add(Constants.PARAM_HISTORY);
704 urlFragments.add(myVersionId);
705 }
706 }
707 }
708 urlFragments.add(Constants.PARAM_TAGS);
709
710 HttpGetClientInvocation invocation = new HttpGetClientInvocation(params, urlFragments);
711
712 return invoke(params, binding, invocation);
713
714 }
715
716 @Override
717 public IGetTags forResource(Class<? extends IResource> theClass) {
718 setResourceClass(theClass);
719 return this;
720 }
721
722 @Override
723 public IGetTags forResource(Class<? extends IResource> theClass, String theId) {
724 setResourceClass(theClass);
725 myId = theId;
726 return this;
727 }
728
729 @Override
730 public IGetTags forResource(Class<? extends IResource> theClass, String theId, String theVersionId) {
731 setResourceClass(theClass);
732 myId = theId;
733 myVersionId = theVersionId;
734 return this;
735 }
736
737 private void setResourceClass(Class<? extends IResource> theClass) {
738 if (theClass != null) {
739 myResourceName = myContext.getResourceDefinition(theClass).getName();
740 } else {
741 myResourceName = null;
742 }
743 }
744
745 }
746
747 private final class LoadPageInternal implements IGetPage {
748
749 @Override
750 public IGetPageTyped next(Bundle theBundle) {
751 return new GetPageInternal(theBundle.getLinkNext().getValue());
752 }
753
754 @Override
755 public IGetPageTyped previous(Bundle theBundle) {
756 return new GetPageInternal(theBundle.getLinkPrevious().getValue());
757 }
758
759 @Override
760 public IGetPageTyped url(String thePageUrl) {
761 return new GetPageInternal(thePageUrl);
762 }
763
764 }
765
766 private final class OperationOutcomeResponseHandler implements IClientResponseHandler<OperationOutcome> {
767
768 @Override
769 public OperationOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
770 BaseServerResponseException {
771 EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
772 if (respType == null) {
773 return null;
774 }
775 IParser parser = respType.newParser(myContext);
776 OperationOutcome retVal;
777 try {
778 retVal = parser.parseResource(OperationOutcome.class, theResponseReader);
779 } catch (DataFormatException e) {
780 ourLog.warn("Failed to parse OperationOutcome response", e);
781 return null;
782 }
783 MethodUtil.parseClientRequestResourceHeaders(theHeaders, retVal);
784
785 return retVal;
786 }
787 }
788
789 private final class OutcomeResponseHandler implements IClientResponseHandler<MethodOutcome> {
790 private final String myResourceName;
791
792 private OutcomeResponseHandler(String theResourceName) {
793 myResourceName = theResourceName;
794 }
795
796 @Override
797 public MethodOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
798 BaseServerResponseException {
799 MethodOutcome response = MethodUtil.process2xxResponse(myContext, myResourceName, theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders);
800 if (theResponseStatusCode == Constants.STATUS_HTTP_201_CREATED) {
801 response.setCreated(true);
802 }
803 return response;
804 }
805 }
806
807 private final class ResourceListResponseHandler implements IClientResponseHandler<List<IResource>> {
808
809 private Class<? extends IResource> myType;
810
811 public ResourceListResponseHandler(Class<? extends IResource> theType) {
812 myType = theType;
813 }
814
815 @Override
816 public List<IResource> invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
817 BaseServerResponseException {
818 return new BundleResponseHandler(myType).invokeClient(theResponseMimeType, theResponseReader, theResponseStatusCode, theHeaders).toListOfResources();
819 }
820 }
821
822 private final class ResourceResponseHandler<T extends IResource> implements IClientResponseHandler<T> {
823
824 private IdDt myId;
825 private Class<T> myType;
826
827 public ResourceResponseHandler(Class<T> theType, IdDt theId) {
828 myType = theType;
829 myId = theId;
830 }
831
832 @Override
833 public T invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
834 EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
835 if (respType == null) {
836 throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
837 }
838 IParser parser = respType.newParser(myContext);
839 T retVal = parser.parseResource(myType, theResponseReader);
840
841 if (myId != null) {
842 retVal.setId(myId);
843 }
844
845 MethodUtil.parseClientRequestResourceHeaders(theHeaders, retVal);
846
847 return retVal;
848 }
849 }
850
851 private class SearchInternal extends BaseClientExecutable<IQuery, Bundle> implements IQuery, IUntypedQuery {
852
853 private String myCompartmentName;
854 private List<ICriterionInternal> myCriterion = new ArrayList<ICriterionInternal>();
855 private List<Include> myInclude = new ArrayList<Include>();
856 private Integer myParamLimit;
857 private String myResourceId;
858 private String myResourceName;
859 private Class<? extends IResource> myResourceType;
860 private SearchStyleEnum mySearchStyle;
861 private List<SortInternal> mySort = new ArrayList<SortInternal>();
862
863 public SearchInternal() {
864 myResourceType = null;
865 myResourceName = null;
866 }
867
868 @Override
869 public IQuery and(ICriterion<?> theCriterion) {
870 myCriterion.add((ICriterionInternal) theCriterion);
871 return this;
872 }
873
874 @Override
875 public Bundle execute() {
876
877 Map<String, List<String>> params = new LinkedHashMap<String, List<String>>();
878
879
880
881
882
883 for (ICriterionInternal next : myCriterion) {
884 String parameterName = next.getParameterName();
885 String parameterValue = next.getParameterValue();
886 addParam(params, parameterName, parameterValue);
887 }
888
889 for (Include next : myInclude) {
890 addParam(params, Constants.PARAM_INCLUDE, next.getValue());
891 }
892
893 for (SortInternal next : mySort) {
894 addParam(params, next.getParamName(), next.getParamValue());
895 }
896
897 if (myParamLimit != null) {
898 addParam(params, Constants.PARAM_COUNT, Integer.toString(myParamLimit));
899 }
900
901 BundleResponseHandler binding = new BundleResponseHandler(myResourceType);
902
903 IdDt resourceId = myResourceId != null ? new IdDt(myResourceId) : null;
904
905 BaseHttpClientInvocation invocation = SearchMethodBinding.createSearchInvocation(myContext, myResourceName, params, resourceId, myCompartmentName, mySearchStyle);
906
907 return invoke(params, binding, invocation);
908
909 }
910
911 @Override
912 public IQuery forAllResources() {
913 return this;
914 }
915
916 @Override
917 public IQuery forResource(Class<? extends IResource> theResourceType) {
918 setType(theResourceType);
919 return this;
920 }
921
922 @Override
923 public IQuery forResource(String theResourceName) {
924 setType(theResourceName);
925 return this;
926 }
927
928 @Override
929 public IQuery include(Include theInclude) {
930 myInclude.add(theInclude);
931 return this;
932 }
933
934 @Override
935 public IQuery limitTo(int theLimitTo) {
936 if (theLimitTo > 0) {
937 myParamLimit = theLimitTo;
938 } else {
939 myParamLimit = null;
940 }
941 return this;
942 }
943
944 private void setType(Class<? extends IResource> theResourceType) {
945 myResourceType = theResourceType;
946 RuntimeResourceDefinition definition = myContext.getResourceDefinition(theResourceType);
947 myResourceName = definition.getName();
948 }
949
950 private void setType(String theResourceName) {
951 myResourceType = myContext.getResourceDefinition(theResourceName).getImplementingClass();
952 myResourceName = theResourceName;
953 }
954
955 @Override
956 public ISort sort() {
957 SortInternal retVal = new SortInternal(this);
958 mySort.add(retVal);
959 return retVal;
960 }
961
962 @Override
963 public IQuery usingStyle(SearchStyleEnum theStyle) {
964 mySearchStyle = theStyle;
965 return this;
966 }
967
968 @Override
969 public IQuery where(ICriterion<?> theCriterion) {
970 myCriterion.add((ICriterionInternal) theCriterion);
971 return this;
972 }
973
974 @Override
975 public IQuery withIdAndCompartment(String theResourceId, String theCompartmentName) {
976 myResourceId = theResourceId;
977 myCompartmentName = theCompartmentName;
978 return this;
979 }
980
981 }
982
983 private static class SortInternal implements ISort {
984
985 private SearchInternal myFor;
986 private String myParamName;
987 private String myParamValue;
988
989 public SortInternal(SearchInternal theFor) {
990 myFor = theFor;
991 }
992
993 @Override
994 public IQuery ascending(IParam theParam) {
995 myParamName = Constants.PARAM_SORT_ASC;
996 myParamValue = theParam.getParamName();
997 return myFor;
998 }
999
1000 @Override
1001 public IQuery defaultOrder(IParam theParam) {
1002 myParamName = Constants.PARAM_SORT;
1003 myParamValue = theParam.getParamName();
1004 return myFor;
1005 }
1006
1007 @Override
1008 public IQuery descending(IParam theParam) {
1009 myParamName = Constants.PARAM_SORT_DESC;
1010 myParamValue = theParam.getParamName();
1011 return myFor;
1012 }
1013
1014 public String getParamName() {
1015 return myParamName;
1016 }
1017
1018 public String getParamValue() {
1019 return myParamValue;
1020 }
1021
1022 }
1023
1024 private final class TagListResponseHandler implements IClientResponseHandler<TagList> {
1025
1026 @Override
1027 public TagList invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
1028 BaseServerResponseException {
1029 EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
1030 if (respType == null) {
1031 throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
1032 }
1033 IParser parser = respType.newParser(myContext);
1034 return parser.parseTagList(theResponseReader);
1035 }
1036 }
1037
1038 private final class TransactionExecutable<T> extends BaseClientExecutable<ITransactionTyped<T>, T> implements ITransactionTyped<T> {
1039
1040 private Bundle myBundle;
1041 private List<IResource> myResources;
1042
1043 public TransactionExecutable(Bundle theResources) {
1044 myBundle = theResources;
1045 }
1046
1047 public TransactionExecutable(List<IResource> theResources) {
1048 myResources = theResources;
1049 }
1050
1051 @SuppressWarnings("unchecked")
1052 @Override
1053 public T execute() {
1054 if (myResources != null) {
1055 ResourceListResponseHandler binding = new ResourceListResponseHandler(null);
1056 BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myResources, myContext);
1057 Map<String, List<String>> params = new HashMap<String, List<String>>();
1058 return (T) invoke(params, binding, invocation);
1059 } else {
1060 BundleResponseHandler binding = new BundleResponseHandler(null);
1061 BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myBundle, myContext);
1062 Map<String, List<String>> params = new HashMap<String, List<String>>();
1063 return (T) invoke(params, binding, invocation);
1064 }
1065 }
1066
1067 }
1068
1069 private final class TransactionInternal implements ITransaction {
1070
1071 @Override
1072 public ITransactionTyped<Bundle> withBundle(Bundle theResources) {
1073 return new TransactionExecutable<Bundle>(theResources);
1074 }
1075
1076 @Override
1077 public ITransactionTyped<List<IResource>> withResources(List<IResource> theResources) {
1078 return new TransactionExecutable<List<IResource>>(theResources);
1079 }
1080
1081 }
1082
1083 private class UpdateInternal extends BaseClientExecutable<IUpdateTyped, MethodOutcome> implements IUpdate, IUpdateTyped {
1084
1085 private IdDt myId;
1086 private IResource myResource;
1087 private String myResourceBody;
1088
1089 @Override
1090 public MethodOutcome execute() {
1091 if (myResource == null) {
1092 myResource = parseResourceBody(myResourceBody);
1093 }
1094 if (myId == null) {
1095 myId = myResource.getId();
1096 }
1097 if (myId == null || myId.hasIdPart() == false) {
1098 throw new InvalidRequestException("No ID supplied for resource to update, can not invoke server");
1099 }
1100
1101 BaseHttpClientInvocation invocation = MethodUtil.createUpdateInvocation(myResource, myResourceBody, myId, myContext);
1102
1103 RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource);
1104 final String resourceName = def.getName();
1105
1106 OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName);
1107
1108 Map<String, List<String>> params = new HashMap<String, List<String>>();
1109 return invoke(params, binding, invocation);
1110
1111 }
1112
1113 @Override
1114 public IUpdateTyped resource(IResource theResource) {
1115 Validate.notNull(theResource, "Resource can not be null");
1116 myResource = theResource;
1117 return this;
1118 }
1119
1120 @Override
1121 public IUpdateTyped resource(String theResourceBody) {
1122 Validate.notBlank(theResourceBody, "Body can not be null or blank");
1123 myResourceBody = theResourceBody;
1124 return this;
1125 }
1126
1127 @Override
1128 public IUpdateTyped withId(IdDt theId) {
1129 if (theId == null) {
1130 throw new NullPointerException("theId can not be null");
1131 }
1132 if (theId.hasIdPart() == false) {
1133 throw new NullPointerException("theId must not be blank and must contain an ID, found: " + theId.getValue());
1134 }
1135 myId = theId;
1136 return this;
1137 }
1138
1139 @Override
1140 public IUpdateTyped withId(String theId) {
1141 if (theId == null) {
1142 throw new NullPointerException("theId can not be null");
1143 }
1144 if (isBlank(theId)) {
1145 throw new NullPointerException("theId must not be blank and must contain an ID, found: " + theId);
1146 }
1147 myId = new IdDt(theId);
1148 return this;
1149 }
1150
1151 }
1152
1153 }