001package ca.uhn.fhir.okhttp.client;
002
003import static ca.uhn.fhir.okhttp.utils.UrlStringUtils.*;
004
005import java.util.List;
006import java.util.Map;
007
008import org.hl7.fhir.instance.model.api.IBaseBinary;
009
010/*
011 * #%L
012 * HAPI FHIR OkHttp Client
013 * %%
014 * Copyright (C) 2014 - 2021 Smile CDR, Inc.
015 * %%
016 * Licensed under the Apache License, Version 2.0 (the "License");
017 * you may not use this file except in compliance with the License.
018 * You may obtain a copy of the License at
019 *
020 *      http://www.apache.org/licenses/LICENSE-2.0
021 *
022 * Unless required by applicable law or agreed to in writing, software
023 * distributed under the License is distributed on an "AS IS" BASIS,
024 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
025 * See the License for the specific language governing permissions and
026 * limitations under the License.
027 * #L%
028 */
029import ca.uhn.fhir.context.FhirContext;
030import ca.uhn.fhir.rest.api.*;
031import ca.uhn.fhir.rest.client.api.*;
032import ca.uhn.fhir.rest.client.impl.BaseHttpClientInvocation;
033import ca.uhn.fhir.rest.client.method.MethodUtil;
034import okhttp3.*;
035import okhttp3.internal.Version;
036
037/**
038 * A Http Request based on OkHttp. This is an adapter around the class
039 * {@link OkHttpClient}
040 *
041 * @author Matthew Clarke | matthew.clarke@orionhealth.com | Orion Health
042 */
043public class OkHttpRestfulClient implements IHttpClient {
044
045    private Call.Factory myClient;
046    private StringBuilder myUrl;
047    private Map<String, List<String>> myIfNoneExistParams;
048    private String myIfNoneExistString;
049    private RequestTypeEnum myRequestType;
050    private List<Header> myHeaders;
051    private OkHttpRestfulRequest myRequest;
052
053    public OkHttpRestfulClient(Call.Factory theClient,
054                               StringBuilder theUrl,
055                               Map<String, List<String>> theIfNoneExistParams,
056                               String theIfNoneExistString,
057                               RequestTypeEnum theRequestType,
058                               List<Header> theHeaders) {
059        myClient = theClient;
060        myUrl = theUrl;
061        myIfNoneExistParams = theIfNoneExistParams;
062        myIfNoneExistString = theIfNoneExistString;
063        myRequestType = theRequestType;
064        myHeaders = theHeaders;
065    }
066
067    @Override
068    public IHttpRequest createByteRequest(FhirContext theContext, String theContents, String theContentType, EncodingEnum theEncoding) {
069        initBaseRequest(theContext, theEncoding, createPostBody(theContents, theContentType));
070        return myRequest;
071    }
072
073    private void initBaseRequest(FhirContext theContext, EncodingEnum theEncoding, RequestBody body) {
074        String sanitisedUrl = withTrailingQuestionMarkRemoved(myUrl.toString());
075        myRequest = new OkHttpRestfulRequest(myClient, sanitisedUrl, myRequestType, body);
076        addHeadersToRequest(myRequest, theEncoding, theContext);
077    }
078
079    private RequestBody createPostBody(String theContents, String theContentType) {
080        return RequestBody.create(MediaType.parse(theContentType), theContents);
081    }
082
083    @Override
084    public IHttpRequest createParamRequest(FhirContext theContext, Map<String, List<String>> theParams, EncodingEnum theEncoding) {
085        initBaseRequest(theContext, theEncoding, getFormBodyFromParams(theParams));
086        return myRequest;
087    }
088
089    private RequestBody getFormBodyFromParams(Map<String, List<String>> queryParams) {
090        FormBody.Builder formBuilder = new FormBody.Builder();
091        for (Map.Entry<String, List<String>> paramEntry : queryParams.entrySet()) {
092            for (String value : paramEntry.getValue()) {
093                formBuilder.add(paramEntry.getKey(), value);
094            }
095        }
096
097        return formBuilder.build();
098    }
099
100    @Override
101    public IHttpRequest createBinaryRequest(FhirContext theContext, IBaseBinary theBinary) {
102        initBaseRequest(theContext, null, createPostBody(theBinary.getContent(), theBinary.getContentType()));
103        return myRequest;
104    }
105
106    private RequestBody createPostBody(byte[] theContents, String theContentType) {
107        return RequestBody.create(MediaType.parse(theContentType), theContents);
108    }
109
110    @Override
111    public IHttpRequest createGetRequest(FhirContext theContext, EncodingEnum theEncoding) {
112        initBaseRequest(theContext, theEncoding, null);
113        return myRequest;
114    }
115
116    private void addHeadersToRequest(OkHttpRestfulRequest theHttpRequest, EncodingEnum theEncoding, FhirContext theContext) {
117        if (myHeaders != null) {
118            for (Header next : myHeaders) {
119                theHttpRequest.addHeader(next.getName(), next.getValue());
120            }
121        }
122
123        addUserAgentHeader(theHttpRequest, theContext);
124        addAcceptCharsetHeader(theHttpRequest);
125        MethodUtil.addAcceptHeaderToRequest(theEncoding, theHttpRequest, theContext);
126        addIfNoneExistHeader(theHttpRequest);
127    }
128
129    private void addUserAgentHeader(OkHttpRestfulRequest theHttpRequest, FhirContext theContext) {
130        theHttpRequest.addHeader("User-Agent", HttpClientUtil.createUserAgentString(theContext, Version.userAgent()));
131    }
132
133    private void addAcceptCharsetHeader(OkHttpRestfulRequest theHttpRequest) {
134        theHttpRequest.addHeader("Accept-Charset", "utf-8");
135    }
136
137    private void addIfNoneExistHeader(IHttpRequest result) {
138        if (myIfNoneExistParams != null) {
139            addIfNoneExistHeaderFromParams(result, myIfNoneExistParams);
140        } else if (myIfNoneExistString != null) {
141            addIfNoneExistHeaderFromString(result, myIfNoneExistString);
142        }
143    }
144
145    private void addIfNoneExistHeaderFromString(IHttpRequest result, String ifNoneExistString) {
146        StringBuilder sb = newHeaderBuilder(myUrl);
147        boolean shouldAddQuestionMark = !hasQuestionMark(sb);
148        sb.append(shouldAddQuestionMark ? '?' : '&');
149        sb.append(everythingAfterFirstQuestionMark(ifNoneExistString));
150        result.addHeader(Constants.HEADER_IF_NONE_EXIST, sb.toString());
151    }
152
153    private void addIfNoneExistHeaderFromParams(IHttpRequest result, Map<String, List<String>> ifNoneExistParams) {
154        StringBuilder sb = newHeaderBuilder(myUrl);
155        boolean shouldAddInitialQuestionMark = !hasQuestionMark(sb);
156        BaseHttpClientInvocation.appendExtraParamsWithQuestionMark(ifNoneExistParams, sb, shouldAddInitialQuestionMark);
157        result.addHeader(Constants.HEADER_IF_NONE_EXIST, sb.toString());
158    }
159
160    public static StringBuilder newHeaderBuilder(StringBuilder baseUrl) {
161        StringBuilder sb = new StringBuilder(baseUrl);
162        if (endsWith(baseUrl, '/')) {
163            deleteLastCharacter(sb);
164        }
165        return sb;
166    }
167
168}