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