001package ca.uhn.fhir.rest.client.impl;
002
003/*
004 * #%L
005 * HAPI FHIR - Client Framework
006 * %%
007 * Copyright (C) 2014 - 2022 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.i18n.Msg;
024import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
025import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
026import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
027import ca.uhn.fhir.context.FhirContext;
028import ca.uhn.fhir.context.FhirVersionEnum;
029import ca.uhn.fhir.context.IRuntimeDatatypeDefinition;
030import ca.uhn.fhir.context.RuntimeResourceDefinition;
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.setResponseHeaders(theHeaders);
1518                        return retVal;
1519                }
1520        }
1521
1522        private final class OutcomeResponseHandler implements IClientResponseHandler<MethodOutcome> {
1523                private PreferReturnEnum myPrefer;
1524
1525                private OutcomeResponseHandler() {
1526                        super();
1527                }
1528
1529                private OutcomeResponseHandler(PreferReturnEnum thePrefer) {
1530                        this();
1531                        myPrefer = thePrefer;
1532                }
1533
1534                @Override
1535                public MethodOutcome invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException {
1536                        MethodOutcome response = MethodUtil.process2xxResponse(myContext, theResponseStatusCode, theResponseMimeType, theResponseInputStream, theHeaders);
1537                        response.setCreatedUsingStatusCode(theResponseStatusCode);
1538
1539                        if (myPrefer == PreferReturnEnum.REPRESENTATION) {
1540                                if (response.getResource() == null) {
1541                                        if (response.getId() != null && isNotBlank(response.getId().getValue()) && response.getId().hasBaseUrl()) {
1542                                                ourLog.info("Server did not return resource for Prefer-representation, going to fetch: {}", response.getId().getValue());
1543                                                IBaseResource resource = read().resource(response.getId().getResourceType()).withUrl(response.getId()).execute();
1544                                                response.setResource(resource);
1545                                        }
1546                                }
1547                        }
1548
1549                        response.setResponseHeaders(theHeaders);
1550
1551                        return response;
1552                }
1553        }
1554
1555        private class PatchInternal extends BaseSearch<IPatchExecutable, IPatchWithQueryTyped, MethodOutcome> implements IPatch, IPatchWithBody, IPatchExecutable, IPatchWithQuery, IPatchWithQueryTyped {
1556
1557                private boolean myConditional;
1558                private IIdType myId;
1559                private String myPatchBody;
1560                private PatchTypeEnum myPatchType;
1561                private PreferReturnEnum myPrefer;
1562                private String myResourceType;
1563                private String mySearchUrl;
1564
1565                @Override
1566                public IPatchWithQuery conditional(Class<? extends IBaseResource> theClass) {
1567                        Validate.notNull(theClass, "theClass must not be null");
1568                        String resourceType = myContext.getResourceType(theClass);
1569                        return conditional(resourceType);
1570                }
1571
1572                @Override
1573                public IPatchWithQuery conditional(String theResourceType) {
1574                        Validate.notBlank(theResourceType, "theResourceType must not be null");
1575                        myResourceType = theResourceType;
1576                        myConditional = true;
1577                        return this;
1578                }
1579
1580                // TODO: This is not longer used.. Deprecate it or just remove it?
1581                @Override
1582                public IPatchWithBody conditionalByUrl(String theSearchUrl) {
1583                        mySearchUrl = validateAndEscapeConditionalUrl(theSearchUrl);
1584                        return this;
1585                }
1586
1587                @Override
1588                public MethodOutcome execute() {
1589
1590                        if (myPatchType == null) {
1591                                throw new InvalidRequestException(Msg.code(1383) + "No patch type supplied, cannot invoke server");
1592                        }
1593                        if (myPatchBody == null) {
1594                                throw new InvalidRequestException(Msg.code(1384) + "No patch body supplied, cannot invoke server");
1595                        }
1596
1597                        BaseHttpClientInvocation invocation;
1598                        if (isNotBlank(mySearchUrl)) {
1599                                invocation = MethodUtil.createPatchInvocation(myContext, mySearchUrl, myPatchType, myPatchBody);
1600                        } else if (myConditional) {
1601                                invocation = MethodUtil.createPatchInvocation(myContext, myPatchType, myPatchBody, myResourceType, getParamMap());
1602                        } else {
1603                                if (myId == null || myId.hasIdPart() == false) {
1604                                        throw new InvalidRequestException(Msg.code(1385) + "No ID supplied for resource to patch, can not invoke server");
1605                                }
1606                                invocation = MethodUtil.createPatchInvocation(myContext, myId, myPatchType, myPatchBody);
1607                        }
1608
1609                        addPreferHeader(myPrefer, invocation);
1610
1611                        OutcomeResponseHandler binding = new OutcomeResponseHandler(myPrefer);
1612
1613                        Map<String, List<String>> params = new HashMap<>();
1614                        return invoke(params, binding, invocation);
1615
1616                }
1617
1618                @Override
1619                public IPatchExecutable prefer(PreferReturnEnum theReturn) {
1620                        myPrefer = theReturn;
1621                        return this;
1622                }
1623
1624                @Override
1625                public IPatchWithBody withBody(String thePatchBody) {
1626                        Validate.notBlank(thePatchBody, "thePatchBody must not be blank");
1627
1628                        myPatchBody = thePatchBody;
1629
1630                        EncodingEnum encoding = EncodingEnum.detectEncodingNoDefault(thePatchBody);
1631                        if (encoding == EncodingEnum.XML) {
1632                                myPatchType = PatchTypeEnum.XML_PATCH;
1633                        } else if (encoding == EncodingEnum.JSON) {
1634                                myPatchType = PatchTypeEnum.JSON_PATCH;
1635                        } else {
1636                                throw new IllegalArgumentException(Msg.code(1386) + "Unable to determine encoding of patch");
1637                        }
1638
1639                        return this;
1640                }
1641
1642                @Override
1643                public IPatchWithBody withFhirPatch(IBaseParameters thePatchBody) {
1644                        Validate.notNull(thePatchBody, "thePatchBody must not be null");
1645
1646                        myPatchType = PatchTypeEnum.FHIR_PATCH_JSON;
1647                        myPatchBody = myContext.newJsonParser().encodeResourceToString(thePatchBody);
1648
1649                        return this;
1650                }
1651
1652                @Override
1653                public IPatchExecutable withId(IIdType theId) {
1654                        if (theId == null) {
1655                                throw new NullPointerException(Msg.code(1387) + "theId can not be null");
1656                        }
1657                        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()));
1658                        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()));
1659                        myId = theId;
1660                        return this;
1661                }
1662
1663                @Override
1664                public IPatchExecutable withId(String theId) {
1665                        if (theId == null) {
1666                                throw new NullPointerException(Msg.code(1388) + "theId can not be null");
1667                        }
1668                        return withId(new IdDt(theId));
1669                }
1670
1671        }
1672
1673        @SuppressWarnings({"rawtypes", "unchecked"})
1674        private class ReadInternal extends BaseClientExecutable implements IRead, IReadTyped, IReadExecutable {
1675                private IIdType myId;
1676                private String myIfVersionMatches;
1677                private ICallable myNotModifiedHandler;
1678                private RuntimeResourceDefinition myType;
1679
1680                @Override
1681                public Object execute() {// AAA
1682                        if (myId.hasVersionIdPart()) {
1683                                return doReadOrVRead(myType.getImplementingClass(), myId, true, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements(), getCustomAcceptHeaderValue(), myCustomHeaderValues);
1684                        }
1685                        return doReadOrVRead(myType.getImplementingClass(), myId, false, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements(), getCustomAcceptHeaderValue(), myCustomHeaderValues);
1686                }
1687
1688                @Override
1689                public IReadIfNoneMatch ifVersionMatches(String theVersion) {
1690                        myIfVersionMatches = theVersion;
1691                        return new IReadIfNoneMatch() {
1692
1693                                @Override
1694                                public IReadExecutable returnNull() {
1695                                        myNotModifiedHandler = new ICallable() {
1696                                                @Override
1697                                                public Object call() {
1698                                                        return null;
1699                                                }
1700                                        };
1701                                        return ReadInternal.this;
1702                                }
1703
1704                                @Override
1705                                public IReadExecutable returnResource(final IBaseResource theInstance) {
1706                                        myNotModifiedHandler = new ICallable() {
1707                                                @Override
1708                                                public Object call() {
1709                                                        return theInstance;
1710                                                }
1711                                        };
1712                                        return ReadInternal.this;
1713                                }
1714
1715                                @Override
1716                                public IReadExecutable throwNotModifiedException() {
1717                                        myNotModifiedHandler = null;
1718                                        return ReadInternal.this;
1719                                }
1720                        };
1721                }
1722
1723                private void processUrl() {
1724                        String resourceType = myId.getResourceType();
1725                        if (isBlank(resourceType)) {
1726                                throw new IllegalArgumentException(Msg.code(1389) + myContext.getLocalizer().getMessage(I18N_INCOMPLETE_URI_FOR_READ, myId));
1727                        }
1728                        myType = myContext.getResourceDefinition(resourceType);
1729                        if (myType == null) {
1730                                throw new IllegalArgumentException(Msg.code(1390) + myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, myId));
1731                        }
1732                }
1733
1734                @Override
1735                public <T extends IBaseResource> IReadTyped<T> resource(Class<T> theResourceType) {
1736                        Validate.notNull(theResourceType, "theResourceType must not be null");
1737                        myType = myContext.getResourceDefinition(theResourceType);
1738                        if (myType == null) {
1739                                throw new IllegalArgumentException(Msg.code(1391) + myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theResourceType));
1740                        }
1741                        return this;
1742                }
1743
1744                @Override
1745                public IReadTyped<IBaseResource> resource(String theResourceAsText) {
1746                        Validate.notBlank(theResourceAsText, "You must supply a value for theResourceAsText");
1747                        myType = myContext.getResourceDefinition(theResourceAsText);
1748                        if (myType == null) {
1749                                throw new IllegalArgumentException(Msg.code(1392) + myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theResourceAsText));
1750                        }
1751                        return this;
1752                }
1753
1754                @Override
1755                public IReadExecutable withId(IIdType theId) {
1756                        Validate.notNull(theId, "The ID can not be null");
1757                        Validate.notBlank(theId.getIdPart(), "The ID can not be blank");
1758                        myId = theId.toUnqualified();
1759                        return this;
1760                }
1761
1762                @Override
1763                public IReadExecutable withId(Long theId) {
1764                        Validate.notNull(theId, "The ID can not be null");
1765                        myId = new IdDt(myType.getName(), theId);
1766                        return this;
1767                }
1768
1769                @Override
1770                public IReadExecutable withId(String theId) {
1771                        Validate.notBlank(theId, "The ID can not be blank");
1772                        if (theId.indexOf('/') == -1) {
1773                                myId = new IdDt(myType.getName(), theId);
1774                        } else {
1775                                myId = new IdDt(theId);
1776                        }
1777                        return this;
1778                }
1779
1780                @Override
1781                public IReadExecutable withIdAndVersion(String theId, String theVersion) {
1782                        Validate.notBlank(theId, "The ID can not be blank");
1783                        myId = new IdDt(myType.getName(), theId, theVersion);
1784                        return this;
1785                }
1786
1787                @Override
1788                public IReadExecutable withUrl(IIdType theUrl) {
1789                        Validate.notNull(theUrl, "theUrl can not be null");
1790                        myId = theUrl;
1791                        processUrl();
1792                        return this;
1793                }
1794
1795                @Override
1796                public IReadExecutable withUrl(String theUrl) {
1797                        myId = new IdDt(theUrl);
1798                        processUrl();
1799                        return this;
1800                }
1801
1802        }
1803
1804        private final class ResourceListResponseHandler implements IClientResponseHandler<List<IBaseResource>> {
1805
1806                @SuppressWarnings("unchecked")
1807                @Override
1808                public List<IBaseResource> invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders)
1809                        throws BaseServerResponseException {
1810                        Class<? extends IBaseResource> bundleType = myContext.getResourceDefinition("Bundle").getImplementingClass();
1811                        ResourceResponseHandler<IBaseResource> handler = new ResourceResponseHandler<>((Class<IBaseResource>) bundleType);
1812                        IBaseResource response = handler.invokeClient(theResponseMimeType, theResponseInputStream, theResponseStatusCode, theHeaders);
1813                        IVersionSpecificBundleFactory bundleFactory = myContext.newBundleFactory();
1814                        bundleFactory.initializeWithBundleResource(response);
1815                        return bundleFactory.toListOfResources();
1816                }
1817        }
1818
1819        @SuppressWarnings({"rawtypes", "unchecked"})
1820        private class SearchInternal<OUTPUT> extends BaseSearch<IQuery<OUTPUT>, IQuery<OUTPUT>, OUTPUT> implements IQuery<OUTPUT>, IUntypedQuery<IQuery<OUTPUT>> {
1821
1822                private String myCompartmentName;
1823                private List<Include> myInclude = new ArrayList<>();
1824                private DateRangeParam myLastUpdated;
1825                private Integer myParamLimit;
1826                private Integer myParamOffset;
1827                private List<Collection<String>> myProfiles = new ArrayList<>();
1828                private String myResourceId;
1829                private String myResourceName;
1830                private Class<? extends IBaseResource> myResourceType;
1831                private Class<? extends IBaseBundle> myReturnBundleType;
1832                private List<Include> myRevInclude = new ArrayList<>();
1833                private SearchStyleEnum mySearchStyle;
1834                private String mySearchUrl;
1835                private List<TokenParam> mySecurity = new ArrayList<>();
1836                private List<SortInternal> mySort = new ArrayList<>();
1837                private List<TokenParam> myTags = new ArrayList<>();
1838                private SearchTotalModeEnum myTotalMode;
1839
1840                public SearchInternal() {
1841                        myResourceType = null;
1842                        myResourceName = null;
1843                        mySearchUrl = null;
1844                }
1845
1846                @Override
1847                public IQuery byUrl(String theSearchUrl) {
1848                        Validate.notBlank(theSearchUrl, "theSearchUrl must not be blank/null");
1849
1850                        if (theSearchUrl.startsWith("http://") || theSearchUrl.startsWith("https://")) {
1851                                mySearchUrl = theSearchUrl;
1852                                int qIndex = mySearchUrl.indexOf('?');
1853                                if (qIndex != -1) {
1854                                        mySearchUrl = mySearchUrl.substring(0, qIndex) + validateAndEscapeConditionalUrl(mySearchUrl.substring(qIndex));
1855                                }
1856                        } else {
1857                                String searchUrl = theSearchUrl;
1858                                if (searchUrl.startsWith("/")) {
1859                                        searchUrl = searchUrl.substring(1);
1860                                }
1861                                if (!searchUrl.matches("[a-zA-Z]+($|\\?.*)")) {
1862                                        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]");
1863                                }
1864                                int qIndex = searchUrl.indexOf('?');
1865                                if (qIndex == -1) {
1866                                        mySearchUrl = getUrlBase() + '/' + searchUrl;
1867                                } else {
1868                                        mySearchUrl = getUrlBase() + '/' + validateAndEscapeConditionalUrl(searchUrl);
1869                                }
1870                        }
1871                        return this;
1872                }
1873
1874                @Override
1875                public IQuery count(int theLimitTo) {
1876                        if (theLimitTo > 0) {
1877                                myParamLimit = theLimitTo;
1878                        } else {
1879                                myParamLimit = null;
1880                        }
1881                        return this;
1882                }
1883
1884                @Override
1885                public IQuery offset(int theOffset) {
1886                        if (theOffset >= 0) {
1887                                myParamOffset = theOffset;
1888                        } else {
1889                                myParamOffset = null;
1890                        }
1891                        return this;
1892                }
1893
1894                @Override
1895                public OUTPUT execute() {
1896
1897                        Map<String, List<String>> params = getParamMap();
1898
1899                        for (TokenParam next : myTags) {
1900                                addParam(params, Constants.PARAM_TAG, next.getValueAsQueryToken(myContext));
1901                        }
1902
1903                        for (TokenParam next : mySecurity) {
1904                                addParam(params, Constants.PARAM_SECURITY, next.getValueAsQueryToken(myContext));
1905                        }
1906
1907                        for (Collection<String> profileUris : myProfiles) {
1908                                StringBuilder builder = new StringBuilder();
1909                                for (Iterator<String> profileItr = profileUris.iterator(); profileItr.hasNext(); ) {
1910                                        builder.append(profileItr.next());
1911                                        if (profileItr.hasNext()) {
1912                                                builder.append(',');
1913                                        }
1914                                }
1915                                addParam(params, Constants.PARAM_PROFILE, builder.toString());
1916                        }
1917
1918                        for (Include next : myInclude) {
1919                                if (next.isRecurse()) {
1920                                        if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
1921                                                addParam(params, Constants.PARAM_INCLUDE_ITERATE, next.getValue());
1922                                        } else {
1923                                                addParam(params, Constants.PARAM_INCLUDE_RECURSE, next.getValue());
1924                                        }
1925                                } else {
1926                                        addParam(params, Constants.PARAM_INCLUDE, next.getValue());
1927                                }
1928                        }
1929
1930                        for (Include next : myRevInclude) {
1931                                if (next.isRecurse()) {
1932                                        if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
1933                                                addParam(params, Constants.PARAM_REVINCLUDE_ITERATE, next.getValue());
1934                                        } else {
1935                                                addParam(params, Constants.PARAM_REVINCLUDE_RECURSE, next.getValue());
1936                                        }
1937                                } else {
1938                                        addParam(params, Constants.PARAM_REVINCLUDE, next.getValue());
1939                                }
1940                        }
1941
1942                        if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2)) {
1943                                SortSpec rootSs = null;
1944                                SortSpec lastSs = null;
1945                                for (SortInternal next : mySort) {
1946                                        SortSpec nextSortSpec = new SortSpec();
1947                                        nextSortSpec.setParamName(next.getParamValue());
1948                                        nextSortSpec.setOrder(next.getDirection());
1949                                        if (rootSs == null) {
1950                                                rootSs = nextSortSpec;
1951                                        } else {
1952                                                lastSs.setChain(nextSortSpec);
1953                                        }
1954                                        lastSs = nextSortSpec;
1955                                }
1956                                if (rootSs != null) {
1957                                        addParam(params, Constants.PARAM_SORT, SortParameter.createSortStringDstu3(rootSs));
1958                                }
1959                        } else {
1960                                for (SortInternal next : mySort) {
1961                                        addParam(params, next.getParamName(), next.getParamValue());
1962                                }
1963                        }
1964
1965                        if (myParamLimit != null) {
1966                                addParam(params, Constants.PARAM_COUNT, Integer.toString(myParamLimit));
1967                        }
1968
1969                        if (myParamOffset != null) {
1970                                addParam(params, Constants.PARAM_OFFSET, Integer.toString(myParamOffset));
1971                        }
1972
1973                        if (myLastUpdated != null) {
1974                                for (DateParam next : myLastUpdated.getValuesAsQueryTokens()) {
1975                                        addParam(params, Constants.PARAM_LASTUPDATED, next.getValueAsQueryToken(myContext));
1976                                }
1977                        }
1978
1979                        if (myTotalMode != null) {
1980                                addParam(params, Constants.PARAM_SEARCH_TOTAL_MODE, myTotalMode.getCode());
1981                        }
1982
1983                        IClientResponseHandler<? extends IBase> binding;
1984                        binding = new ResourceResponseHandler(myReturnBundleType, getPreferResponseTypes(myResourceType));
1985
1986                        IdDt resourceId = myResourceId != null ? new IdDt(myResourceId) : null;
1987
1988                        BaseHttpClientInvocation invocation;
1989                        if (mySearchUrl != null) {
1990                                invocation = SearchMethodBinding.createSearchInvocation(myContext, mySearchUrl, UrlSourceEnum.EXPLICIT, params);
1991                        } else {
1992                                invocation = SearchMethodBinding.createSearchInvocation(myContext, myResourceName, params, resourceId, myCompartmentName, mySearchStyle);
1993                        }
1994
1995                        return (OUTPUT) invoke(params, binding, invocation);
1996
1997                }
1998
1999                @Override
2000                public IQuery forAllResources() {
2001                        return this;
2002                }
2003
2004                @Override
2005                public IQuery forResource(Class theResourceType) {
2006                        setType(theResourceType);
2007                        return this;
2008                }
2009
2010                @Override
2011                public IQuery forResource(String theResourceName) {
2012                        setType(theResourceName);
2013                        return this;
2014                }
2015
2016                @Override
2017                public IQuery include(Include theInclude) {
2018                        myInclude.add(theInclude);
2019                        return this;
2020                }
2021
2022                @Override
2023                public IQuery lastUpdated(DateRangeParam theLastUpdated) {
2024                        myLastUpdated = theLastUpdated;
2025                        return this;
2026                }
2027
2028                @Deprecated // override deprecated method
2029                @Override
2030                public IQuery limitTo(int theLimitTo) {
2031                        return count(theLimitTo);
2032                }
2033
2034                @Override
2035                public IQuery<OUTPUT> totalMode(SearchTotalModeEnum theSearchTotalModeEnum) {
2036                        myTotalMode = theSearchTotalModeEnum;
2037                        return this;
2038                }
2039
2040                @Override
2041                public IQuery returnBundle(Class theClass) {
2042                        if (theClass == null) {
2043                                throw new NullPointerException(Msg.code(1394) + "theClass must not be null");
2044                        }
2045                        myReturnBundleType = theClass;
2046                        return this;
2047                }
2048
2049                @Override
2050                public IQuery revInclude(Include theInclude) {
2051                        myRevInclude.add(theInclude);
2052                        return this;
2053                }
2054
2055                private void setType(Class<? extends IBaseResource> theResourceType) {
2056                        myResourceType = theResourceType;
2057                        RuntimeResourceDefinition definition = myContext.getResourceDefinition(theResourceType);
2058                        myResourceName = definition.getName();
2059                }
2060
2061                private void setType(String theResourceName) {
2062                        myResourceType = myContext.getResourceDefinition(theResourceName).getImplementingClass();
2063                        myResourceName = theResourceName;
2064                }
2065
2066                @Override
2067                public ISort sort() {
2068                        SortInternal retVal = new SortInternal(this);
2069                        mySort.add(retVal);
2070                        return retVal;
2071                }
2072
2073                @Override
2074                public IQuery sort(SortSpec theSortSpec) {
2075                        SortSpec sortSpec = theSortSpec;
2076                        while (sortSpec != null) {
2077                                mySort.add(new SortInternal(sortSpec));
2078                                sortSpec = sortSpec.getChain();
2079                        }
2080                        return this;
2081                }
2082
2083                @Override
2084                public IQuery usingStyle(SearchStyleEnum theStyle) {
2085                        mySearchStyle = theStyle;
2086                        return this;
2087                }
2088
2089                @Override
2090                public IQuery withAnyProfile(Collection theProfileUris) {
2091                        Validate.notEmpty(theProfileUris, "theProfileUris must not be null or empty");
2092                        myProfiles.add(theProfileUris);
2093                        return this;
2094                }
2095
2096                @Override
2097                public IQuery withIdAndCompartment(String theResourceId, String theCompartmentName) {
2098                        myResourceId = theResourceId;
2099                        myCompartmentName = theCompartmentName;
2100                        return this;
2101                }
2102
2103                @Override
2104                public IQuery withProfile(String theProfileUri) {
2105                        Validate.notBlank(theProfileUri, "theProfileUri must not be null or empty");
2106                        myProfiles.add(Collections.singletonList(theProfileUri));
2107                        return this;
2108                }
2109
2110                @Override
2111                public IQuery withSecurity(String theSystem, String theCode) {
2112                        Validate.notBlank(theCode, "theCode must not be null or empty");
2113                        mySecurity.add(new TokenParam(theSystem, theCode));
2114                        return this;
2115                }
2116
2117                @Override
2118                public IQuery withTag(String theSystem, String theCode) {
2119                        Validate.notBlank(theCode, "theCode must not be null or empty");
2120                        myTags.add(new TokenParam(theSystem, theCode));
2121                        return this;
2122                }
2123
2124        }
2125
2126        private final class StringResponseHandler implements IClientResponseHandler<String> {
2127
2128                @Override
2129                public String invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders)
2130                        throws IOException, BaseServerResponseException {
2131                        return IOUtils.toString(theResponseInputStream, Charsets.UTF_8);
2132                }
2133        }
2134
2135        private final class TransactionExecutable<T> extends BaseClientExecutable<ITransactionTyped<T>, T> implements ITransactionTyped<T> {
2136
2137                private IBaseBundle myBaseBundle;
2138                private String myRawBundle;
2139                private EncodingEnum myRawBundleEncoding;
2140                private List<? extends IBaseResource> myResources;
2141
2142                public TransactionExecutable(IBaseBundle theBundle) {
2143                        myBaseBundle = theBundle;
2144                }
2145
2146                public TransactionExecutable(List<? extends IBaseResource> theResources) {
2147                        myResources = theResources;
2148                }
2149
2150                public TransactionExecutable(String theBundle) {
2151                        myRawBundle = theBundle;
2152                        myRawBundleEncoding = EncodingEnum.detectEncodingNoDefault(myRawBundle);
2153                        if (myRawBundleEncoding == null) {
2154                                throw new IllegalArgumentException(Msg.code(1395) + myContext.getLocalizer().getMessage(GenericClient.class, "cantDetermineRequestType"));
2155                        }
2156                }
2157
2158                @SuppressWarnings({"unchecked", "rawtypes"})
2159                @Override
2160                public T execute() {
2161                        Map<String, List<String>> params = new HashMap<String, List<String>>();
2162                        if (myResources != null) {
2163                                ResourceListResponseHandler binding = new ResourceListResponseHandler();
2164                                BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myResources, myContext);
2165                                return (T) invoke(params, binding, invocation);
2166                        } else if (myBaseBundle != null) {
2167                                ResourceResponseHandler binding = new ResourceResponseHandler(myBaseBundle.getClass(), getPreferResponseTypes());
2168                                BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myBaseBundle, myContext);
2169                                return (T) invoke(params, binding, invocation);
2170                                // } else if (myRawBundle != null) {
2171                        } else {
2172                                StringResponseHandler binding = new StringResponseHandler();
2173                                /*
2174                                 * If the user has explicitly requested a given encoding, we may need to re-encode the raw string
2175                                 */
2176                                if (getParamEncoding() != null) {
2177                                        if (EncodingEnum.detectEncodingNoDefault(myRawBundle) != getParamEncoding()) {
2178                                                IBaseResource parsed = parseResourceBody(myRawBundle);
2179                                                myRawBundle = getParamEncoding().newParser(getFhirContext()).encodeResourceToString(parsed);
2180                                        }
2181                                }
2182                                BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myRawBundle, myContext);
2183                                return (T) invoke(params, binding, invocation);
2184                        }
2185                }
2186
2187        }
2188
2189        private final class TransactionInternal implements ITransaction {
2190
2191                @Override
2192                public ITransactionTyped<String> withBundle(String theBundle) {
2193                        Validate.notBlank(theBundle, "theBundle must not be null");
2194                        return new TransactionExecutable<String>(theBundle);
2195                }
2196
2197                @Override
2198                public <T extends IBaseBundle> ITransactionTyped<T> withBundle(T theBundle) {
2199                        Validate.notNull(theBundle, "theBundle must not be null");
2200                        return new TransactionExecutable<T>(theBundle);
2201                }
2202
2203                @Override
2204                public ITransactionTyped<List<IBaseResource>> withResources(List<? extends IBaseResource> theResources) {
2205                        Validate.notNull(theResources, "theResources must not be null");
2206
2207                        for (IBaseResource next : theResources) {
2208                                String entryMethod = null;
2209                                if (next instanceof IResource) {
2210                                        BundleEntryTransactionMethodEnum entryMethodEnum = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get((IResource) next);
2211                                        if (entryMethodEnum != null) {
2212                                                entryMethod = entryMethodEnum.getCode();
2213                                        }
2214                                } else {
2215                                        entryMethod = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get((IAnyResource) next);
2216                                }
2217
2218                                if (isBlank(entryMethod)) {
2219                                        if (isBlank(next.getIdElement().getValue())) {
2220                                                entryMethod = "POST";
2221                                        } else {
2222                                                entryMethod = "PUT";
2223                                        }
2224                                        if (next instanceof IResource) {
2225                                                ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put((IResource) next, BundleEntryTransactionMethodEnum.valueOf(entryMethod));
2226                                        } else {
2227                                                ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put((IAnyResource) next, entryMethod);
2228                                        }
2229
2230                                }
2231
2232                        }
2233
2234                        return new TransactionExecutable<>(theResources);
2235                }
2236
2237        }
2238
2239        private class UpdateInternal extends BaseSearch<IUpdateExecutable, IUpdateWithQueryTyped, MethodOutcome>
2240                implements IUpdate, IUpdateTyped, IUpdateExecutable, IUpdateWithQuery, IUpdateWithQueryTyped {
2241
2242                private boolean myConditional;
2243                private IIdType myId;
2244                private PreferReturnEnum myPrefer;
2245                private IBaseResource myResource;
2246                private String myResourceBody;
2247                private String mySearchUrl;
2248
2249                @Override
2250                public IUpdateWithQuery conditional() {
2251                        myConditional = true;
2252                        return this;
2253                }
2254
2255                @Override
2256                public IUpdateTyped conditionalByUrl(String theSearchUrl) {
2257                        mySearchUrl = validateAndEscapeConditionalUrl(theSearchUrl);
2258                        return this;
2259                }
2260
2261                @Override
2262                public MethodOutcome execute() {
2263                        if (myResource == null) {
2264                                myResource = parseResourceBody(myResourceBody);
2265                        }
2266
2267                        // If an explicit encoding is chosen, we will re-serialize to ensure the right encoding
2268                        if (getParamEncoding() != null) {
2269                                myResourceBody = null;
2270                        }
2271
2272                        BaseHttpClientInvocation invocation;
2273                        if (mySearchUrl != null) {
2274                                invocation = MethodUtil.createUpdateInvocation(myContext, myResource, myResourceBody, mySearchUrl);
2275                        } else if (myConditional) {
2276                                invocation = MethodUtil.createUpdateInvocation(myContext, myResource, myResourceBody, getParamMap());
2277                        } else {
2278                                if (myId == null) {
2279                                        myId = myResource.getIdElement();
2280                                }
2281
2282                                if (myId == null || myId.hasIdPart() == false) {
2283                                        throw new InvalidRequestException(Msg.code(1396) + "No ID supplied for resource to update, can not invoke server");
2284                                }
2285                                invocation = MethodUtil.createUpdateInvocation(myResource, myResourceBody, myId, myContext);
2286                        }
2287
2288                        addPreferHeader(myPrefer, invocation);
2289
2290                        OutcomeResponseHandler binding = new OutcomeResponseHandler(myPrefer);
2291
2292                        Map<String, List<String>> params = new HashMap<String, List<String>>();
2293                        return invoke(params, binding, invocation);
2294
2295                }
2296
2297                @Override
2298                public IUpdateExecutable prefer(PreferReturnEnum theReturn) {
2299                        myPrefer = theReturn;
2300                        return this;
2301                }
2302
2303                @Override
2304                public IUpdateTyped resource(IBaseResource theResource) {
2305                        Validate.notNull(theResource, "Resource can not be null");
2306                        myResource = theResource;
2307                        return this;
2308                }
2309
2310                @Override
2311                public IUpdateTyped resource(String theResourceBody) {
2312                        Validate.notBlank(theResourceBody, "Body can not be null or blank");
2313                        myResourceBody = theResourceBody;
2314                        return this;
2315                }
2316
2317                @Override
2318                public IUpdateExecutable withId(IIdType theId) {
2319                        if (theId == null) {
2320                                throw new NullPointerException(Msg.code(1397) + "theId can not be null");
2321                        }
2322                        if (theId.hasIdPart() == false) {
2323                                throw new NullPointerException(Msg.code(1398) + "theId must not be blank and must contain an ID, found: " + theId.getValue());
2324                        }
2325                        myId = theId;
2326                        return this;
2327                }
2328
2329                @Override
2330                public IUpdateExecutable withId(String theId) {
2331                        if (theId == null) {
2332                                throw new NullPointerException(Msg.code(1399) + "theId can not be null");
2333                        }
2334                        if (isBlank(theId)) {
2335                                throw new NullPointerException(Msg.code(1400) + "theId must not be blank and must contain an ID, found: " + theId);
2336                        }
2337                        myId = new IdDt(theId);
2338                        return this;
2339                }
2340
2341        }
2342
2343        private class ValidateInternal extends BaseClientExecutable<IValidateUntyped, MethodOutcome> implements IValidate, IValidateUntyped {
2344                private IBaseResource myResource;
2345
2346                @Override
2347                public MethodOutcome execute() {
2348                        BaseHttpClientInvocation invocation = ValidateMethodBindingDstu2Plus.createValidateInvocation(myContext, myResource);
2349                        ResourceResponseHandler<BaseOperationOutcome> handler = new ResourceResponseHandler<BaseOperationOutcome>(null, null);
2350                        IBaseOperationOutcome outcome = invoke(null, handler, invocation);
2351                        MethodOutcome retVal = new MethodOutcome();
2352                        retVal.setOperationOutcome(outcome);
2353                        return retVal;
2354                }
2355
2356                @Override
2357                public IValidateUntyped resource(IBaseResource theResource) {
2358                        Validate.notNull(theResource, "theResource must not be null");
2359                        myResource = theResource;
2360                        return this;
2361                }
2362
2363                @Override
2364                public IValidateUntyped resource(String theResourceRaw) {
2365                        Validate.notBlank(theResourceRaw, "theResourceRaw must not be null or blank");
2366                        myResource = parseResourceBody(theResourceRaw);
2367
2368                        EncodingEnum enc = EncodingEnum.detectEncodingNoDefault(theResourceRaw);
2369                        if (enc == null) {
2370                                throw new IllegalArgumentException(Msg.code(1401) + myContext.getLocalizer().getMessage(GenericClient.class, "cantDetermineRequestType"));
2371                        }
2372                        switch (enc) {
2373                                case XML:
2374                                        encodedXml();
2375                                        break;
2376                                case JSON:
2377                                        encodedJson();
2378                                        break;
2379                        }
2380                        return this;
2381                }
2382
2383        }
2384
2385        @SuppressWarnings("rawtypes")
2386        private static class SortInternal implements ISort {
2387
2388                private SortOrderEnum myDirection;
2389                private SearchInternal myFor;
2390                private String myParamName;
2391                private String myParamValue;
2392
2393                public SortInternal(SearchInternal theFor) {
2394                        myFor = theFor;
2395                }
2396
2397                public SortInternal(SortSpec theSortSpec) {
2398                        if (theSortSpec.getOrder() == null) {
2399                                myParamName = Constants.PARAM_SORT;
2400                        } else if (theSortSpec.getOrder() == SortOrderEnum.ASC) {
2401                                myParamName = Constants.PARAM_SORT_ASC;
2402                        } else if (theSortSpec.getOrder() == SortOrderEnum.DESC) {
2403                                myParamName = Constants.PARAM_SORT_DESC;
2404                        }
2405                        myDirection = theSortSpec.getOrder();
2406                        myParamValue = theSortSpec.getParamName();
2407                }
2408
2409                @Override
2410                public IQuery ascending(IParam theParam) {
2411                        myParamName = Constants.PARAM_SORT_ASC;
2412                        myDirection = SortOrderEnum.ASC;
2413                        myParamValue = theParam.getParamName();
2414                        return myFor;
2415                }
2416
2417                @Override
2418                public IQuery ascending(String theParam) {
2419                        myParamName = Constants.PARAM_SORT_ASC;
2420                        myDirection = SortOrderEnum.ASC;
2421                        myParamValue = theParam;
2422                        return myFor;
2423                }
2424
2425                @Override
2426                public IQuery defaultOrder(IParam theParam) {
2427                        myParamName = Constants.PARAM_SORT;
2428                        myDirection = null;
2429                        myParamValue = theParam.getParamName();
2430                        return myFor;
2431                }
2432
2433                @Override
2434                public IQuery defaultOrder(String theParam) {
2435                        myParamName = Constants.PARAM_SORT;
2436                        myDirection = null;
2437                        myParamValue = theParam;
2438                        return myFor;
2439                }
2440
2441                @Override
2442                public IQuery descending(IParam theParam) {
2443                        myParamName = Constants.PARAM_SORT_DESC;
2444                        myDirection = SortOrderEnum.DESC;
2445                        myParamValue = theParam.getParamName();
2446                        return myFor;
2447                }
2448
2449                @Override
2450                public IQuery descending(String theParam) {
2451                        myParamName = Constants.PARAM_SORT_DESC;
2452                        myDirection = SortOrderEnum.DESC;
2453                        myParamValue = theParam;
2454                        return myFor;
2455                }
2456
2457                public SortOrderEnum getDirection() {
2458                        return myDirection;
2459                }
2460
2461                public String getParamName() {
2462                        return myParamName;
2463                }
2464
2465                public String getParamValue() {
2466                        return myParamValue;
2467                }
2468
2469        }
2470
2471        private static void addParam(Map<String, List<String>> params, String parameterName, String parameterValue) {
2472                if (!params.containsKey(parameterName)) {
2473                        params.put(parameterName, new ArrayList<String>());
2474                }
2475                params.get(parameterName).add(parameterValue);
2476        }
2477
2478        private static void addPreferHeader(PreferReturnEnum thePrefer, BaseHttpClientInvocation theInvocation) {
2479                if (thePrefer != null) {
2480                        theInvocation.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + '=' + thePrefer.getHeaderValue());
2481                }
2482        }
2483
2484        private static String validateAndEscapeConditionalUrl(String theSearchUrl) {
2485                Validate.notBlank(theSearchUrl, "Conditional URL can not be blank/null");
2486                StringBuilder b = new StringBuilder();
2487                boolean haveHadQuestionMark = false;
2488                for (int i = 0; i < theSearchUrl.length(); i++) {
2489                        char nextChar = theSearchUrl.charAt(i);
2490                        if (!haveHadQuestionMark) {
2491                                if (nextChar == '?') {
2492                                        haveHadQuestionMark = true;
2493                                } else if (!Character.isLetter(nextChar)) {
2494                                        throw new IllegalArgumentException(Msg.code(1402) + "Conditional URL must be in the format \"[ResourceType]?[Params]\" and must not have a base URL - Found: " + theSearchUrl);
2495                                }
2496                                b.append(nextChar);
2497                        } else {
2498                                switch (nextChar) {
2499                                        case '|':
2500                                        case '?':
2501                                        case '$':
2502                                        case ':':
2503                                                b.append(UrlUtil.escapeUrlParam(Character.toString(nextChar)));
2504                                                break;
2505                                        default:
2506                                                b.append(nextChar);
2507                                                break;
2508                                }
2509                        }
2510                }
2511                return b.toString();
2512        }
2513
2514}