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