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