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