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 returnStringOutcome() {
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                private boolean myIsHistoryRewrite;
1745
1746                @Override
1747                public IPatchWithQuery conditional(Class<? extends IBaseResource> theClass) {
1748                        Validate.notNull(theClass, "theClass must not be null");
1749                        String resourceType = myContext.getResourceType(theClass);
1750                        return conditional(resourceType);
1751                }
1752
1753                @Override
1754                public IPatchWithQuery conditional(String theResourceType) {
1755                        Validate.notBlank(theResourceType, "theResourceType must not be null");
1756                        myResourceType = theResourceType;
1757                        myConditional = true;
1758                        return this;
1759                }
1760
1761                // TODO: This is not longer used.. Deprecate it or just remove it?
1762                @Override
1763                public IPatchWithBody conditionalByUrl(String theSearchUrl) {
1764                        mySearchUrl = validateAndEscapeConditionalUrl(theSearchUrl);
1765                        return this;
1766                }
1767
1768                @Override
1769                public MethodOutcome execute() {
1770
1771                        if (myPatchType == null) {
1772                                throw new InvalidRequestException(Msg.code(1383) + "No patch type supplied, cannot invoke server");
1773                        }
1774                        if (myPatchBody == null) {
1775                                throw new InvalidRequestException(Msg.code(1384) + "No patch body supplied, cannot invoke server");
1776                        }
1777
1778                        BaseHttpClientInvocation invocation;
1779                        if (myIsHistoryRewrite) {
1780                                if (!myId.hasVersionIdPart()) {
1781                                        throw new InvalidRequestException(Msg.code(2716)
1782                                                        + "Invalid resource ID for rewrite history: ID must contain a history version");
1783                                }
1784                                // createPatchInvocation() eventually calls HttpPatchClientInvocation
1785                                // passing in the ID as a URL (String) will keep the version whereas passing in the raw ID will remove
1786                                // the version
1787                                invocation = MethodUtil.createPatchInvocation(myContext, myId.getValue(), myPatchType, myPatchBody);
1788                                invocation.addHeader(Constants.HEADER_REWRITE_HISTORY, "true");
1789                        } else if (isNotBlank(mySearchUrl)) {
1790                                invocation = MethodUtil.createPatchInvocation(myContext, mySearchUrl, myPatchType, myPatchBody);
1791                        } else if (myConditional) {
1792                                invocation = MethodUtil.createPatchInvocation(
1793                                                myContext, myPatchType, myPatchBody, myResourceType, getParamMap());
1794                        } else {
1795                                if (myId == null || !myId.hasIdPart()) {
1796                                        throw new InvalidRequestException(
1797                                                        Msg.code(1385) + "No ID supplied for resource to patch, can not invoke server");
1798                                }
1799                                invocation = MethodUtil.createPatchInvocation(myContext, myId, myPatchType, myPatchBody);
1800                        }
1801
1802                        addPreferHeader(myPrefer, invocation);
1803
1804                        OutcomeResponseHandler binding = new OutcomeResponseHandler(myPrefer);
1805
1806                        Map<String, List<String>> params = new HashMap<>();
1807                        return invoke(params, binding, invocation);
1808                }
1809
1810                @Override
1811                public IPatchExecutable prefer(PreferReturnEnum theReturn) {
1812                        myPrefer = theReturn;
1813                        return this;
1814                }
1815
1816                @Override
1817                public IPatchWithBody withBody(String thePatchBody) {
1818                        Validate.notBlank(thePatchBody, "thePatchBody must not be blank");
1819
1820                        myPatchBody = thePatchBody;
1821
1822                        EncodingEnum encoding = EncodingEnum.detectEncodingNoDefault(thePatchBody);
1823                        if (encoding == EncodingEnum.XML) {
1824                                myPatchType = PatchTypeEnum.XML_PATCH;
1825                        } else if (encoding == EncodingEnum.JSON) {
1826                                myPatchType = PatchTypeEnum.JSON_PATCH;
1827                        } else {
1828                                throw new IllegalArgumentException(Msg.code(1386) + "Unable to determine encoding of patch");
1829                        }
1830
1831                        return this;
1832                }
1833
1834                @Override
1835                public IPatchWithBody withFhirPatch(IBaseParameters thePatchBody) {
1836                        Validate.notNull(thePatchBody, "thePatchBody must not be null");
1837
1838                        myPatchType = PatchTypeEnum.FHIR_PATCH_JSON;
1839                        myPatchBody = myContext.newJsonParser().encodeResourceToString(thePatchBody);
1840
1841                        return this;
1842                }
1843
1844                @Override
1845                public IPatchExecutable withId(IIdType theId) {
1846                        if (theId == null) {
1847                                throw new NullPointerException(Msg.code(1387) + "theId can not be null");
1848                        }
1849                        Validate.notBlank(
1850                                        theId.getIdPart(),
1851                                        "theId must not be blank and must contain a resource type and ID (e.g. \"Patient/123\"), found: %s",
1852                                        UrlUtil.sanitizeUrlPart(theId.getValue()));
1853                        Validate.notBlank(
1854                                        theId.getResourceType(),
1855                                        "theId must not be blank and must contain a resource type and ID (e.g. \"Patient/123\"), found: %s",
1856                                        UrlUtil.sanitizeUrlPart(theId.getValue()));
1857                        myId = theId;
1858                        return this;
1859                }
1860
1861                @Override
1862                public IPatchExecutable withId(String theId) {
1863                        if (theId == null) {
1864                                throw new NullPointerException(Msg.code(1388) + "theId can not be null");
1865                        }
1866                        return withId(new IdDt(theId));
1867                }
1868
1869                @Override
1870                public IPatchWithBody historyRewrite() {
1871                        myIsHistoryRewrite = true;
1872                        return this;
1873                }
1874        }
1875
1876        @SuppressWarnings({"rawtypes", "unchecked"})
1877        private class ReadInternal extends BaseClientExecutable implements IRead, IReadTyped, IReadExecutable {
1878                private IIdType myId;
1879                private String myIfVersionMatches;
1880                private ICallable myNotModifiedHandler;
1881                private RuntimeResourceDefinition myType;
1882
1883                @Override
1884                public Object execute() { // AAA
1885                        if (myId.hasVersionIdPart()) {
1886                                return doReadOrVRead(
1887                                                myType.getImplementingClass(),
1888                                                myId,
1889                                                true,
1890                                                myNotModifiedHandler,
1891                                                myIfVersionMatches,
1892                                                myPrettyPrint,
1893                                                mySummaryMode,
1894                                                myParamEncoding,
1895                                                getSubsetElements(),
1896                                                getCustomAcceptHeaderValue(),
1897                                                myCustomHeaderValues);
1898                        }
1899                        return doReadOrVRead(
1900                                        myType.getImplementingClass(),
1901                                        myId,
1902                                        false,
1903                                        myNotModifiedHandler,
1904                                        myIfVersionMatches,
1905                                        myPrettyPrint,
1906                                        mySummaryMode,
1907                                        myParamEncoding,
1908                                        getSubsetElements(),
1909                                        getCustomAcceptHeaderValue(),
1910                                        myCustomHeaderValues);
1911                }
1912
1913                @Override
1914                public IReadIfNoneMatch ifVersionMatches(String theVersion) {
1915                        myIfVersionMatches = theVersion;
1916                        return new IReadIfNoneMatch() {
1917
1918                                @Override
1919                                public IReadExecutable returnNull() {
1920                                        myNotModifiedHandler = () -> null;
1921                                        return ReadInternal.this;
1922                                }
1923
1924                                @Override
1925                                public IReadExecutable returnResource(final IBaseResource theInstance) {
1926                                        myNotModifiedHandler = () -> theInstance;
1927                                        return ReadInternal.this;
1928                                }
1929
1930                                @Override
1931                                public IReadExecutable throwNotModifiedException() {
1932                                        myNotModifiedHandler = null;
1933                                        return ReadInternal.this;
1934                                }
1935                        };
1936                }
1937
1938                private void processUrl() {
1939                        String resourceType = myId.getResourceType();
1940                        if (isBlank(resourceType)) {
1941                                throw new IllegalArgumentException(
1942                                                Msg.code(1389) + myContext.getLocalizer().getMessage(I18N_INCOMPLETE_URI_FOR_READ, myId));
1943                        }
1944                        myType = myContext.getResourceDefinition(resourceType);
1945                        if (myType == null) {
1946                                throw new IllegalArgumentException(
1947                                                Msg.code(1390) + myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, myId));
1948                        }
1949                }
1950
1951                @Override
1952                public <T extends IBaseResource> IReadTyped<T> resource(Class<T> theResourceType) {
1953                        Validate.notNull(theResourceType, "theResourceType must not be null");
1954                        myType = myContext.getResourceDefinition(theResourceType);
1955                        if (myType == null) {
1956                                throw new IllegalArgumentException(Msg.code(1391)
1957                                                + myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theResourceType));
1958                        }
1959                        return this;
1960                }
1961
1962                @Override
1963                public IReadTyped<IBaseResource> resource(String theResourceAsText) {
1964                        Validate.notBlank(theResourceAsText, "You must supply a value for theResourceAsText");
1965                        myType = myContext.getResourceDefinition(theResourceAsText);
1966                        if (myType == null) {
1967                                throw new IllegalArgumentException(Msg.code(1392)
1968                                                + myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theResourceAsText));
1969                        }
1970                        return this;
1971                }
1972
1973                @Override
1974                public IReadExecutable withId(IIdType theId) {
1975                        Validate.notNull(theId, "The ID can not be null");
1976                        Validate.notBlank(theId.getIdPart(), "The ID can not be blank");
1977                        myId = theId.toUnqualified();
1978                        return this;
1979                }
1980
1981                @Override
1982                public IReadExecutable withId(Long theId) {
1983                        Validate.notNull(theId, "The ID can not be null");
1984                        myId = new IdDt(myType.getName(), theId);
1985                        return this;
1986                }
1987
1988                @Override
1989                public IReadExecutable withId(String theId) {
1990                        Validate.notBlank(theId, "The ID can not be blank");
1991                        if (theId.indexOf('/') == -1) {
1992                                myId = new IdDt(myType.getName(), theId);
1993                        } else {
1994                                myId = new IdDt(theId);
1995                        }
1996                        return this;
1997                }
1998
1999                @Override
2000                public IReadExecutable withIdAndVersion(String theId, String theVersion) {
2001                        Validate.notBlank(theId, "The ID can not be blank");
2002                        myId = new IdDt(myType.getName(), theId, theVersion);
2003                        return this;
2004                }
2005
2006                @Override
2007                public IReadExecutable withUrl(IIdType theUrl) {
2008                        Validate.notNull(theUrl, "theUrl can not be null");
2009                        myId = theUrl;
2010                        processUrl();
2011                        return this;
2012                }
2013
2014                @Override
2015                public IReadExecutable withUrl(String theUrl) {
2016                        myId = new IdDt(theUrl);
2017                        processUrl();
2018                        return this;
2019                }
2020        }
2021
2022        private final class ResourceListResponseHandler implements IClientResponseHandler<List<IBaseResource>> {
2023
2024                @SuppressWarnings("unchecked")
2025                @Override
2026                public List<IBaseResource> invokeClient(
2027                                String theResponseMimeType,
2028                                InputStream theResponseInputStream,
2029                                int theResponseStatusCode,
2030                                Map<String, List<String>> theHeaders)
2031                                throws BaseServerResponseException {
2032                        Class<? extends IBaseResource> bundleType =
2033                                        myContext.getResourceDefinition("Bundle").getImplementingClass();
2034                        ResourceResponseHandler<IBaseResource> handler =
2035                                        new ResourceResponseHandler<>((Class<IBaseResource>) bundleType);
2036                        IBaseResource response = handler.invokeClient(
2037                                        theResponseMimeType, theResponseInputStream, theResponseStatusCode, theHeaders);
2038                        IVersionSpecificBundleFactory bundleFactory = myContext.newBundleFactory();
2039                        bundleFactory.initializeWithBundleResource(response);
2040                        return bundleFactory.toListOfResources();
2041                }
2042        }
2043
2044        @SuppressWarnings({"rawtypes", "unchecked"})
2045        private class SearchInternal<OUTPUT> extends BaseSearch<IQuery<OUTPUT>, IQuery<OUTPUT>, OUTPUT>
2046                        implements IQuery<OUTPUT>, IUntypedQuery<IQuery<OUTPUT>> {
2047
2048                private String myCompartmentName;
2049                private final List<Include> myInclude = new ArrayList<>();
2050                private DateRangeParam myLastUpdated;
2051                private Integer myParamLimit;
2052                private Integer myParamOffset;
2053                private final List<Collection<String>> myProfiles = new ArrayList<>();
2054                private String myResourceId;
2055                private String myResourceName;
2056                private Class<? extends IBaseResource> myResourceType;
2057                private Class<? extends IBaseBundle> myReturnBundleType;
2058                private final List<Include> myRevInclude = new ArrayList<>();
2059                private SearchStyleEnum mySearchStyle;
2060                private String mySearchUrl;
2061                private final List<TokenParam> mySecurity = new ArrayList<>();
2062                private final List<SortInternal> mySort = new ArrayList<>();
2063                private final List<TokenParam> myTags = new ArrayList<>();
2064                private SearchTotalModeEnum myTotalMode;
2065
2066                public SearchInternal() {
2067                        myResourceType = null;
2068                        myResourceName = null;
2069                        mySearchUrl = null;
2070                }
2071
2072                @Override
2073                public IQuery byUrl(String theSearchUrl) {
2074                        Validate.notBlank(theSearchUrl, "theSearchUrl must not be blank/null");
2075
2076                        if (theSearchUrl.startsWith("http://") || theSearchUrl.startsWith("https://")) {
2077                                mySearchUrl = theSearchUrl;
2078                                int qIndex = mySearchUrl.indexOf('?');
2079                                if (qIndex != -1) {
2080                                        mySearchUrl = mySearchUrl.substring(0, qIndex)
2081                                                        + validateAndEscapeConditionalUrl(mySearchUrl.substring(qIndex));
2082                                }
2083                        } else {
2084                                String searchUrl = theSearchUrl;
2085                                if (searchUrl.startsWith("/")) {
2086                                        searchUrl = searchUrl.substring(1);
2087                                }
2088                                if (!searchUrl.matches("[a-zA-Z]+($|\\?.*)")) {
2089                                        throw new IllegalArgumentException(
2090                                                        Msg.code(1393)
2091                                                                        + "Search URL must be either a complete URL starting with http: or https:, or a relative FHIR URL in the form [ResourceType]?[Params]");
2092                                }
2093                                int qIndex = searchUrl.indexOf('?');
2094                                if (qIndex == -1) {
2095                                        mySearchUrl = getUrlBase() + '/' + searchUrl;
2096                                } else {
2097                                        mySearchUrl = getUrlBase() + '/' + validateAndEscapeConditionalUrl(searchUrl);
2098                                }
2099                        }
2100                        return this;
2101                }
2102
2103                @Override
2104                public IQuery count(int theLimitTo) {
2105                        if (theLimitTo > 0) {
2106                                myParamLimit = theLimitTo;
2107                        } else {
2108                                myParamLimit = null;
2109                        }
2110                        return this;
2111                }
2112
2113                @Override
2114                public IQuery offset(int theOffset) {
2115                        if (theOffset >= 0) {
2116                                myParamOffset = theOffset;
2117                        } else {
2118                                myParamOffset = null;
2119                        }
2120                        return this;
2121                }
2122
2123                @Override
2124                public OUTPUT execute() {
2125
2126                        Map<String, List<String>> params = getParamMap();
2127
2128                        for (TokenParam next : myTags) {
2129                                addParam(params, Constants.PARAM_TAG, next.getValueAsQueryToken(myContext));
2130                        }
2131
2132                        for (TokenParam next : mySecurity) {
2133                                addParam(params, Constants.PARAM_SECURITY, next.getValueAsQueryToken(myContext));
2134                        }
2135
2136                        for (Collection<String> profileUris : myProfiles) {
2137                                StringBuilder builder = new StringBuilder();
2138                                for (Iterator<String> profileItr = profileUris.iterator(); profileItr.hasNext(); ) {
2139                                        builder.append(profileItr.next());
2140                                        if (profileItr.hasNext()) {
2141                                                builder.append(',');
2142                                        }
2143                                }
2144                                addParam(params, Constants.PARAM_PROFILE, builder.toString());
2145                        }
2146
2147                        for (Include next : myInclude) {
2148                                if (next.isRecurse()) {
2149                                        if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
2150                                                addParam(params, Constants.PARAM_INCLUDE_ITERATE, next.getValue());
2151                                        } else {
2152                                                addParam(params, Constants.PARAM_INCLUDE_RECURSE, next.getValue());
2153                                        }
2154                                } else {
2155                                        addParam(params, Constants.PARAM_INCLUDE, next.getValue());
2156                                }
2157                        }
2158
2159                        for (Include next : myRevInclude) {
2160                                if (next.isRecurse()) {
2161                                        if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
2162                                                addParam(params, Constants.PARAM_REVINCLUDE_ITERATE, next.getValue());
2163                                        } else {
2164                                                addParam(params, Constants.PARAM_REVINCLUDE_RECURSE, next.getValue());
2165                                        }
2166                                } else {
2167                                        addParam(params, Constants.PARAM_REVINCLUDE, next.getValue());
2168                                }
2169                        }
2170
2171                        if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2)) {
2172                                SortSpec rootSs = null;
2173                                SortSpec lastSs = null;
2174                                for (SortInternal next : mySort) {
2175                                        SortSpec nextSortSpec = new SortSpec();
2176                                        nextSortSpec.setParamName(next.getParamValue());
2177                                        nextSortSpec.setOrder(next.getDirection());
2178                                        if (rootSs == null) {
2179                                                rootSs = nextSortSpec;
2180                                        } else {
2181                                                lastSs.setChain(nextSortSpec);
2182                                        }
2183                                        lastSs = nextSortSpec;
2184                                }
2185                                if (rootSs != null) {
2186                                        addParam(params, Constants.PARAM_SORT, SortParameter.createSortStringDstu3(rootSs));
2187                                }
2188                        } else {
2189                                for (SortInternal next : mySort) {
2190                                        addParam(params, next.getParamName(), next.getParamValue());
2191                                }
2192                        }
2193
2194                        if (myParamLimit != null) {
2195                                addParam(params, Constants.PARAM_COUNT, Integer.toString(myParamLimit));
2196                        }
2197
2198                        if (myParamOffset != null) {
2199                                addParam(params, Constants.PARAM_OFFSET, Integer.toString(myParamOffset));
2200                        }
2201
2202                        if (myLastUpdated != null) {
2203                                for (DateParam next : myLastUpdated.getValuesAsQueryTokens()) {
2204                                        addParam(params, Constants.PARAM_LASTUPDATED, next.getValueAsQueryToken(myContext));
2205                                }
2206                        }
2207
2208                        if (myTotalMode != null) {
2209                                addParam(params, Constants.PARAM_SEARCH_TOTAL_MODE, myTotalMode.getCode());
2210                        }
2211
2212                        IClientResponseHandler<? extends IBase> binding;
2213                        binding = new ResourceResponseHandler(myReturnBundleType, getPreferResponseTypes(myResourceType));
2214
2215                        IdDt resourceId = myResourceId != null ? new IdDt(myResourceId) : null;
2216
2217                        BaseHttpClientInvocation invocation;
2218                        if (mySearchUrl != null) {
2219                                invocation = SearchMethodBinding.createSearchInvocation(
2220                                                myContext, mySearchUrl, UrlSourceEnum.EXPLICIT, params);
2221                        } else {
2222                                invocation = SearchMethodBinding.createSearchInvocation(
2223                                                myContext, myResourceName, params, resourceId, myCompartmentName, mySearchStyle);
2224                        }
2225
2226                        return (OUTPUT) invoke(params, binding, invocation);
2227                }
2228
2229                @Override
2230                public IQuery forAllResources() {
2231                        return this;
2232                }
2233
2234                @Override
2235                public IQuery forResource(Class theResourceType) {
2236                        setType(theResourceType);
2237                        return this;
2238                }
2239
2240                @Override
2241                public IQuery forResource(String theResourceName) {
2242                        setType(theResourceName);
2243                        return this;
2244                }
2245
2246                @Override
2247                public IQuery include(Include theInclude) {
2248                        myInclude.add(theInclude);
2249                        return this;
2250                }
2251
2252                @Override
2253                public IQuery lastUpdated(DateRangeParam theLastUpdated) {
2254                        myLastUpdated = theLastUpdated;
2255                        return this;
2256                }
2257
2258                @Deprecated // override deprecated method
2259                @Override
2260                public IQuery limitTo(int theLimitTo) {
2261                        return count(theLimitTo);
2262                }
2263
2264                @Override
2265                public IQuery<OUTPUT> totalMode(SearchTotalModeEnum theSearchTotalModeEnum) {
2266                        myTotalMode = theSearchTotalModeEnum;
2267                        return this;
2268                }
2269
2270                @Override
2271                public IQuery returnBundle(Class theClass) {
2272                        if (theClass == null) {
2273                                throw new NullPointerException(Msg.code(1394) + "theClass must not be null");
2274                        }
2275                        myReturnBundleType = theClass;
2276                        return this;
2277                }
2278
2279                @Override
2280                public IQuery revInclude(Include theInclude) {
2281                        myRevInclude.add(theInclude);
2282                        return this;
2283                }
2284
2285                private void setType(Class<? extends IBaseResource> theResourceType) {
2286                        myResourceType = theResourceType;
2287                        RuntimeResourceDefinition definition = myContext.getResourceDefinition(theResourceType);
2288                        myResourceName = definition.getName();
2289                }
2290
2291                private void setType(String theResourceName) {
2292                        myResourceType = myContext.getResourceDefinition(theResourceName).getImplementingClass();
2293                        myResourceName = theResourceName;
2294                }
2295
2296                @Override
2297                public ISort sort() {
2298                        SortInternal retVal = new SortInternal(this);
2299                        mySort.add(retVal);
2300                        return retVal;
2301                }
2302
2303                @Override
2304                public IQuery sort(SortSpec theSortSpec) {
2305                        SortSpec sortSpec = theSortSpec;
2306                        while (sortSpec != null) {
2307                                mySort.add(new SortInternal(sortSpec));
2308                                sortSpec = sortSpec.getChain();
2309                        }
2310                        return this;
2311                }
2312
2313                @Override
2314                public IQuery usingStyle(SearchStyleEnum theStyle) {
2315                        mySearchStyle = theStyle;
2316                        return this;
2317                }
2318
2319                @Override
2320                public IQuery withAnyProfile(Collection theProfileUris) {
2321                        Validate.notEmpty(theProfileUris, "theProfileUris must not be null or empty");
2322                        myProfiles.add(theProfileUris);
2323                        return this;
2324                }
2325
2326                @Override
2327                public IQuery withIdAndCompartment(String theResourceId, String theCompartmentName) {
2328                        myResourceId = theResourceId;
2329                        myCompartmentName = theCompartmentName;
2330                        return this;
2331                }
2332
2333                @Override
2334                public IQuery withProfile(String theProfileUri) {
2335                        Validate.notBlank(theProfileUri, "theProfileUri must not be null or empty");
2336                        myProfiles.add(Collections.singletonList(theProfileUri));
2337                        return this;
2338                }
2339
2340                @Override
2341                public IQuery withSecurity(String theSystem, String theCode) {
2342                        Validate.notBlank(theCode, "theCode must not be null or empty");
2343                        mySecurity.add(new TokenParam(theSystem, theCode));
2344                        return this;
2345                }
2346
2347                @Override
2348                public IQuery withTag(String theSystem, String theCode) {
2349                        Validate.notBlank(theCode, "theCode must not be null or empty");
2350                        myTags.add(new TokenParam(theSystem, theCode));
2351                        return this;
2352                }
2353        }
2354
2355        private static final class StringResponseHandler implements IClientResponseHandler<String> {
2356
2357                @Override
2358                public String invokeClient(
2359                                String theResponseMimeType,
2360                                InputStream theResponseInputStream,
2361                                int theResponseStatusCode,
2362                                Map<String, List<String>> theHeaders)
2363                                throws IOException, BaseServerResponseException {
2364                        return IOUtils.toString(theResponseInputStream, Charsets.UTF_8);
2365                }
2366        }
2367
2368        private static final class StringOutcomeResponseHandler implements IClientResponseHandler<StringOutcome> {
2369
2370                @Override
2371                public StringOutcome invokeClient(
2372                                String theResponseMimeType,
2373                                InputStream theResponseInputStream,
2374                                int theResponseStatusCode,
2375                                Map<String, List<String>> theHeaders)
2376                                throws IOException, BaseServerResponseException {
2377
2378                        String payload = IOUtils.toString(theResponseInputStream, Charsets.UTF_8);
2379                        return new StringOutcome(theResponseStatusCode, payload, theHeaders);
2380                }
2381        }
2382
2383        private final class TransactionExecutable<T> extends BaseClientExecutable<ITransactionTyped<T>, T>
2384                        implements ITransactionTyped<T> {
2385
2386                private IBaseBundle myBaseBundle;
2387                private String myRawBundle;
2388                private EncodingEnum myRawBundleEncoding;
2389                private List<? extends IBaseResource> myResources;
2390
2391                public TransactionExecutable(IBaseBundle theBundle) {
2392                        myBaseBundle = theBundle;
2393                }
2394
2395                public TransactionExecutable(List<? extends IBaseResource> theResources) {
2396                        myResources = theResources;
2397                }
2398
2399                public TransactionExecutable(String theBundle) {
2400                        myRawBundle = theBundle;
2401                        myRawBundleEncoding = EncodingEnum.detectEncodingNoDefault(myRawBundle);
2402                        if (myRawBundleEncoding == null) {
2403                                throw new IllegalArgumentException(Msg.code(1395)
2404                                                + myContext.getLocalizer().getMessage(GenericClient.class, "cantDetermineRequestType"));
2405                        }
2406                }
2407
2408                @SuppressWarnings({"unchecked", "rawtypes"})
2409                @Override
2410                public T execute() {
2411                        Map<String, List<String>> params = new HashMap<>();
2412                        if (myResources != null) {
2413                                ResourceListResponseHandler binding = new ResourceListResponseHandler();
2414                                BaseHttpClientInvocation invocation =
2415                                                TransactionMethodBinding.createTransactionInvocation(myResources, myContext);
2416                                return (T) invoke(params, binding, invocation);
2417                        } else if (myBaseBundle != null) {
2418                                ResourceResponseHandler binding =
2419                                                new ResourceResponseHandler(myBaseBundle.getClass(), getPreferResponseTypes());
2420                                BaseHttpClientInvocation invocation =
2421                                                TransactionMethodBinding.createTransactionInvocation(myBaseBundle, myContext);
2422                                return (T) invoke(params, binding, invocation);
2423                                // } else if (myRawBundle != null) {
2424                        } else {
2425                                StringResponseHandler binding = new StringResponseHandler();
2426                                /*
2427                                 * If the user has explicitly requested a given encoding, we may need to re-encode the raw string
2428                                 */
2429                                if (getParamEncoding() != null) {
2430                                        if (EncodingEnum.detectEncodingNoDefault(myRawBundle) != getParamEncoding()) {
2431                                                IBaseResource parsed = parseResourceBody(myRawBundle);
2432                                                myRawBundle =
2433                                                                getParamEncoding().newParser(getFhirContext()).encodeResourceToString(parsed);
2434                                        }
2435                                }
2436                                BaseHttpClientInvocation invocation =
2437                                                TransactionMethodBinding.createTransactionInvocation(myRawBundle, myContext);
2438                                return (T) invoke(params, binding, invocation);
2439                        }
2440                }
2441        }
2442
2443        private final class TransactionInternal implements ITransaction {
2444
2445                @Override
2446                public ITransactionTyped<String> withBundle(String theBundle) {
2447                        Validate.notBlank(theBundle, "theBundle must not be null");
2448                        return new TransactionExecutable<>(theBundle);
2449                }
2450
2451                @Override
2452                public <T extends IBaseBundle> ITransactionTyped<T> withBundle(T theBundle) {
2453                        Validate.notNull(theBundle, "theBundle must not be null");
2454                        return new TransactionExecutable<>(theBundle);
2455                }
2456
2457                @Override
2458                public ITransactionTyped<List<IBaseResource>> withResources(List<? extends IBaseResource> theResources) {
2459                        Validate.notNull(theResources, "theResources must not be null");
2460
2461                        for (IBaseResource next : theResources) {
2462                                BundleEntryTransactionMethodEnum entryMethod =
2463                                                ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(next);
2464
2465                                if (entryMethod == null) {
2466                                        if (isBlank(next.getIdElement().getValue())) {
2467                                                entryMethod = BundleEntryTransactionMethodEnum.POST;
2468                                        } else {
2469                                                entryMethod = BundleEntryTransactionMethodEnum.PUT;
2470                                        }
2471                                        ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(next, entryMethod);
2472                                }
2473                        }
2474
2475                        return new TransactionExecutable<>(theResources);
2476                }
2477        }
2478
2479        private class UpdateInternal extends BaseSearch<IUpdateExecutable, IUpdateWithQueryTyped, MethodOutcome>
2480                        implements IUpdate, IUpdateTyped, IUpdateExecutable, IUpdateWithQuery, IUpdateWithQueryTyped {
2481
2482                private boolean myConditional;
2483                private IIdType myId;
2484                private PreferReturnEnum myPrefer;
2485                private IBaseResource myResource;
2486                private String myResourceBody;
2487                private String mySearchUrl;
2488                private boolean myIsHistoryRewrite;
2489
2490                @Override
2491                public IUpdateTyped historyRewrite() {
2492                        myIsHistoryRewrite = true;
2493                        return this;
2494                }
2495
2496                @Override
2497                public IUpdateWithQuery conditional() {
2498                        myConditional = true;
2499                        return this;
2500                }
2501
2502                @Override
2503                public IUpdateTyped conditionalByUrl(String theSearchUrl) {
2504                        mySearchUrl = validateAndEscapeConditionalUrl(theSearchUrl);
2505                        return this;
2506                }
2507
2508                @Override
2509                public MethodOutcome execute() {
2510                        if (myResource == null) {
2511                                myResource = parseResourceBody(myResourceBody);
2512                        }
2513
2514                        // If an explicit encoding is chosen, we will re-serialize to ensure the right encoding
2515                        if (getParamEncoding() != null) {
2516                                myResourceBody = null;
2517                        }
2518
2519                        BaseHttpClientInvocation invocation;
2520                        if (mySearchUrl != null) {
2521                                invocation = MethodUtil.createUpdateInvocation(myContext, myResource, myResourceBody, mySearchUrl);
2522                        } else if (myConditional) {
2523                                invocation = MethodUtil.createUpdateInvocation(myContext, myResource, myResourceBody, getParamMap());
2524                        } else {
2525                                if (myId == null) {
2526                                        myId = myResource.getIdElement();
2527                                }
2528
2529                                if (myId == null || !myId.hasIdPart()) {
2530                                        throw new InvalidRequestException(
2531                                                        Msg.code(1396) + "No ID supplied for resource to update, can not invoke server");
2532                                }
2533
2534                                if (myIsHistoryRewrite) {
2535                                        if (!myId.hasVersionIdPart()) {
2536                                                throw new InvalidRequestException(Msg.code(2090) + "ID must contain a history version, found: "
2537                                                                + myId.getVersionIdPart());
2538                                        }
2539                                        invocation = MethodUtil.createUpdateHistoryRewriteInvocation(
2540                                                        myResource, myResourceBody, myId, myContext);
2541                                        invocation.addHeader(Constants.HEADER_REWRITE_HISTORY, "true");
2542                                } else {
2543                                        invocation = MethodUtil.createUpdateInvocation(myResource, myResourceBody, myId, myContext);
2544                                }
2545                        }
2546
2547                        addPreferHeader(myPrefer, invocation);
2548
2549                        OutcomeResponseHandler binding = new OutcomeResponseHandler(myPrefer);
2550
2551                        Map<String, List<String>> params = new HashMap<>();
2552                        return invoke(params, binding, invocation);
2553                }
2554
2555                @Override
2556                public IUpdateExecutable prefer(PreferReturnEnum theReturn) {
2557                        myPrefer = theReturn;
2558                        return this;
2559                }
2560
2561                @Override
2562                public IUpdateTyped resource(IBaseResource theResource) {
2563                        Validate.notNull(theResource, "Resource can not be null");
2564                        myResource = theResource;
2565                        return this;
2566                }
2567
2568                @Override
2569                public IUpdateTyped resource(String theResourceBody) {
2570                        Validate.notBlank(theResourceBody, "Body can not be null or blank");
2571                        myResourceBody = theResourceBody;
2572                        return this;
2573                }
2574
2575                @Override
2576                public IUpdateExecutable withId(IIdType theId) {
2577                        if (theId == null) {
2578                                throw new NullPointerException(Msg.code(1397) + "theId can not be null");
2579                        }
2580                        if (!theId.hasIdPart()) {
2581                                throw new NullPointerException(
2582                                                Msg.code(1398) + "theId must not be blank and must contain an ID, found: " + theId.getValue());
2583                        }
2584                        myId = theId;
2585                        return this;
2586                }
2587
2588                @Override
2589                public IUpdateExecutable withId(String theId) {
2590                        if (theId == null) {
2591                                throw new NullPointerException(Msg.code(1399) + "theId can not be null");
2592                        }
2593                        if (isBlank(theId)) {
2594                                throw new NullPointerException(
2595                                                Msg.code(1400) + "theId must not be blank and must contain an ID, found: " + theId);
2596                        }
2597                        myId = new IdDt(theId);
2598                        return this;
2599                }
2600        }
2601
2602        private class ValidateInternal extends BaseClientExecutable<IValidateUntyped, MethodOutcome>
2603                        implements IValidate, IValidateUntyped {
2604                private IBaseResource myResource;
2605
2606                @Override
2607                public MethodOutcome execute() {
2608                        BaseHttpClientInvocation invocation =
2609                                        ValidateMethodBindingDstu2Plus.createValidateInvocation(myContext, myResource);
2610                        ResourceResponseHandler<BaseOperationOutcome> handler = new ResourceResponseHandler<>(null, null);
2611                        MethodOutcomeResponseHandler methodHandler = new MethodOutcomeResponseHandler(handler);
2612                        return invoke(null, methodHandler, invocation);
2613                }
2614
2615                @Override
2616                public IValidateUntyped resource(IBaseResource theResource) {
2617                        Validate.notNull(theResource, "theResource must not be null");
2618                        myResource = theResource;
2619                        return this;
2620                }
2621
2622                @Override
2623                public IValidateUntyped resource(String theResourceRaw) {
2624                        Validate.notBlank(theResourceRaw, "theResourceRaw must not be null or blank");
2625                        myResource = parseResourceBody(theResourceRaw);
2626
2627                        EncodingEnum enc = EncodingEnum.detectEncodingNoDefault(theResourceRaw);
2628                        if (enc == null) {
2629                                throw new IllegalArgumentException(Msg.code(1401)
2630                                                + myContext.getLocalizer().getMessage(GenericClient.class, "cantDetermineRequestType"));
2631                        }
2632                        switch (enc) {
2633                                case XML:
2634                                        encodedXml();
2635                                        break;
2636                                case JSON:
2637                                        encodedJson();
2638                                        break;
2639                        }
2640                        return this;
2641                }
2642        }
2643
2644        @SuppressWarnings("rawtypes")
2645        private static class SortInternal implements ISort {
2646
2647                private SortOrderEnum myDirection;
2648                private SearchInternal myFor;
2649                private String myParamName;
2650                private String myParamValue;
2651
2652                public SortInternal(SearchInternal theFor) {
2653                        myFor = theFor;
2654                }
2655
2656                public SortInternal(SortSpec theSortSpec) {
2657                        if (theSortSpec.getOrder() == null) {
2658                                myParamName = Constants.PARAM_SORT;
2659                        } else if (theSortSpec.getOrder() == SortOrderEnum.ASC) {
2660                                myParamName = Constants.PARAM_SORT_ASC;
2661                        } else if (theSortSpec.getOrder() == SortOrderEnum.DESC) {
2662                                myParamName = Constants.PARAM_SORT_DESC;
2663                        }
2664                        myDirection = theSortSpec.getOrder();
2665                        myParamValue = theSortSpec.getParamName();
2666                }
2667
2668                @Override
2669                public IQuery ascending(IParam theParam) {
2670                        myParamName = Constants.PARAM_SORT_ASC;
2671                        myDirection = SortOrderEnum.ASC;
2672                        myParamValue = theParam.getParamName();
2673                        return myFor;
2674                }
2675
2676                @Override
2677                public IQuery ascending(String theParam) {
2678                        myParamName = Constants.PARAM_SORT_ASC;
2679                        myDirection = SortOrderEnum.ASC;
2680                        myParamValue = theParam;
2681                        return myFor;
2682                }
2683
2684                @Override
2685                public IQuery defaultOrder(IParam theParam) {
2686                        myParamName = Constants.PARAM_SORT;
2687                        myDirection = null;
2688                        myParamValue = theParam.getParamName();
2689                        return myFor;
2690                }
2691
2692                @Override
2693                public IQuery defaultOrder(String theParam) {
2694                        myParamName = Constants.PARAM_SORT;
2695                        myDirection = null;
2696                        myParamValue = theParam;
2697                        return myFor;
2698                }
2699
2700                @Override
2701                public IQuery descending(IParam theParam) {
2702                        myParamName = Constants.PARAM_SORT_DESC;
2703                        myDirection = SortOrderEnum.DESC;
2704                        myParamValue = theParam.getParamName();
2705                        return myFor;
2706                }
2707
2708                @Override
2709                public IQuery descending(String theParam) {
2710                        myParamName = Constants.PARAM_SORT_DESC;
2711                        myDirection = SortOrderEnum.DESC;
2712                        myParamValue = theParam;
2713                        return myFor;
2714                }
2715
2716                public SortOrderEnum getDirection() {
2717                        return myDirection;
2718                }
2719
2720                public String getParamName() {
2721                        return myParamName;
2722                }
2723
2724                public String getParamValue() {
2725                        return myParamValue;
2726                }
2727        }
2728
2729        private static void addParam(Map<String, List<String>> params, String parameterName, String parameterValue) {
2730                if (!params.containsKey(parameterName)) {
2731                        params.put(parameterName, new ArrayList<>());
2732                }
2733                params.get(parameterName).add(parameterValue);
2734        }
2735
2736        private static void addPreferHeader(PreferReturnEnum thePrefer, BaseHttpClientInvocation theInvocation) {
2737                if (thePrefer != null) {
2738                        theInvocation.addHeader(
2739                                        Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + '=' + thePrefer.getHeaderValue());
2740                }
2741        }
2742
2743        private static String validateAndEscapeConditionalUrl(String theSearchUrl) {
2744                Validate.notBlank(theSearchUrl, "Conditional URL can not be blank/null");
2745                StringBuilder b = new StringBuilder();
2746                boolean haveHadQuestionMark = false;
2747                for (int i = 0; i < theSearchUrl.length(); i++) {
2748                        char nextChar = theSearchUrl.charAt(i);
2749                        if (!haveHadQuestionMark) {
2750                                if (nextChar == '?') {
2751                                        haveHadQuestionMark = true;
2752                                } else if (!Character.isLetter(nextChar)) {
2753                                        throw new IllegalArgumentException(Msg.code(1402)
2754                                                        + "Conditional URL must be in the format \"[ResourceType]?[Params]\" and must not have a base URL - Found: "
2755                                                        + theSearchUrl);
2756                                }
2757                                b.append(nextChar);
2758                        } else {
2759                                switch (nextChar) {
2760                                        case '|':
2761                                        case '?':
2762                                        case '$':
2763                                        case ':':
2764                                                b.append(UrlUtil.escapeUrlParam(Character.toString(nextChar)));
2765                                                break;
2766                                        default:
2767                                                b.append(nextChar);
2768                                                break;
2769                                }
2770                        }
2771                }
2772                return b.toString();
2773        }
2774}