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