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