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