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