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