View Javadoc
1   package ca.uhn.fhir.rest.client;
2   
3   /*
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
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   * @author James Agnew
93   * @author Doug Martin (Regenstrief Center for Biomedical Informatics)
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 	 * For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
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 	// public IResource read(UriDt url) {
157 	// return read(inferResourceClass(url), url);
158 	// }
159 	//
160 	// @SuppressWarnings("unchecked")
161 	// public <T extends IResource> T read(final Class<T> theType, UriDt url) {
162 	// return (T) invoke(theType, url, new ResourceResponseHandler<T>(theType));
163 	// }
164 	//
165 	// public Bundle search(UriDt url) {
166 	// return search(inferResourceClass(url), url);
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 	 * For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
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 	/* also deprecated in interface */
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 			// if (myParamEncoding != null) {
496 			// theParams.put(Constants.PARAM_FORMAT, Collections.singletonList(myParamEncoding.getFormatContentType()));
497 			// }
498 			//
499 			// if (myPrettyPrint != null) {
500 			// theParams.put(Constants.PARAM_PRETTY, Collections.singletonList(myPrettyPrint.toString()));
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 			// Map<String, List<String>> initial = createExtraParams();
879 			// if (initial != null) {
880 			// params.putAll(initial);
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 }