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