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