001/*
002 *   Copyright 2024 Vonage
003 *
004 *   Licensed under the Apache License, Version 2.0 (the "License");
005 *   you may not use this file except in compliance with the License.
006 *   You may obtain a copy of the License at
007 *
008 *        http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *   Unless required by applicable law or agreed to in writing, software
011 *   distributed under the License is distributed on an "AS IS" BASIS,
012 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *   See the License for the specific language governing permissions and
014 *   limitations under the License.
015 */
016package com.vonage.client;
017
018import com.vonage.client.auth.*;
019import org.apache.commons.logging.LogFactory;
020import org.apache.http.HttpHeaders;
021import org.apache.http.HttpResponse;
022import org.apache.http.client.methods.CloseableHttpResponse;
023import org.apache.http.client.methods.HttpUriRequest;
024import org.apache.http.client.methods.RequestBuilder;
025import java.io.IOException;
026import java.nio.charset.StandardCharsets;
027import java.util.AbstractMap;
028import java.util.Set;
029import java.util.stream.Collectors;
030
031/**
032 * Abstract class to assist in implementing a call against a REST endpoint.
033 * <p>
034 * Concrete implementations must implement {@link #makeRequest(Object)} to construct a {@link RequestBuilder} from the
035 * provided parameterized request object, and {@link #parseResponse(HttpResponse)} to construct the parameterized {@link
036 * HttpResponse} object.
037 * <p>
038 * The REST call is executed by calling {@link #execute(Object)}.
039 *
040 * @param <RequestT> The type of the method-specific request object that will be used to construct an HTTP request
041 * @param <ResultT>  The type of method-specific response object which will be constructed from the returned HTTP
042 *                   response
043 */
044public abstract class AbstractMethod<RequestT, ResultT> implements RestEndpoint<RequestT, ResultT> {
045    static {
046        LogFactory.getLog(AbstractMethod.class);
047    }
048    
049    protected final HttpWrapper httpWrapper;
050
051    public AbstractMethod(HttpWrapper httpWrapper) {
052        this.httpWrapper = httpWrapper;
053    }
054
055    public HttpWrapper getHttpWrapper() {
056        return httpWrapper;
057    }
058
059    protected ResultT postProcessParsedResponse(ResultT response) {
060        return response;
061    }
062
063    /**
064     * Execute the REST call represented by this method object.
065     *
066     * @param request A RequestT representing input to the REST call to be made
067     *
068     * @return A ResultT representing the response from the executed REST call
069     *
070     * @throws VonageClientException if there is a problem parsing the HTTP response
071     */
072    @Override
073    public ResultT execute(RequestT request) throws VonageResponseParseException, VonageClientException {
074        HttpUriRequest httpRequest = applyAuth(makeRequest(request))
075                .setHeader(HttpHeaders.USER_AGENT, httpWrapper.getUserAgent())
076                .setCharset(StandardCharsets.UTF_8).build();
077
078        try (CloseableHttpResponse response = httpWrapper.getHttpClient().execute(httpRequest)) {
079            try {
080                return postProcessParsedResponse(parseResponse(response));
081            }
082            catch (IOException iox) {
083                throw new VonageResponseParseException(iox);
084            }
085        }
086        catch (IOException iox) {
087            throw new VonageMethodFailedException("Something went wrong while executing the HTTP request.", iox);
088        }
089    }
090
091    /**
092     * Apply an appropriate authentication method (specified by {@link #getAcceptableAuthMethods()}) to the provided
093     * {@link RequestBuilder}, and return the result.
094     *
095     * @param request A RequestBuilder which has not yet had authentication information applied
096     *
097     * @return A RequestBuilder with appropriate authentication information applied (may or not be the same instance as
098     * <pre>request</pre>)
099     *
100     * @throws VonageClientException If no appropriate {@link AuthMethod} is available
101     */
102    final RequestBuilder applyAuth(RequestBuilder request) throws VonageClientException {
103        AuthMethod am = getAuthMethod();
104        if (am instanceof HeaderAuthMethod) {
105            request.setHeader("Authorization", ((HeaderAuthMethod) am).getHeaderValue());
106        }
107        if (am instanceof QueryParamsAuthMethod) {
108            RequestQueryParams qp = am instanceof ApiKeyQueryParamsAuthMethod ? null : normalRequestParams(request);
109            ((QueryParamsAuthMethod) am).getAuthParams(qp).forEach(request::addParameter);
110        }
111        return request;
112    }
113
114    static RequestQueryParams normalRequestParams(RequestBuilder request) {
115        return request.getParameters().stream()
116                .map(nvp -> new AbstractMap.SimpleEntry<>(nvp.getName(), nvp.getValue()))
117                .collect(Collectors.toCollection(RequestQueryParams::new));
118    }
119
120    /**
121     * Gets the highest priority available authentication method according to its sort key.
122     *
123     * @return An AuthMethod created from the accepted auth methods.
124     * @throws VonageUnexpectedException If no AuthMethod is available.
125     */
126    protected AuthMethod getAuthMethod() throws VonageUnexpectedException {
127        return httpWrapper.getAuthCollection().getAcceptableAuthMethod(getAcceptableAuthMethods());
128    }
129
130    protected abstract Set<Class<? extends AuthMethod>> getAcceptableAuthMethods();
131
132    /**
133     * Construct and return a RequestBuilder instance from the provided request.
134     *
135     * @param request A RequestT representing input to the REST call to be made
136     *
137     * @return A ResultT representing the response from the executed REST call
138     */
139    protected abstract RequestBuilder makeRequest(RequestT request);
140
141    /**
142     * Construct a ResultT representing the contents of the HTTP response returned from the Vonage Voice API.
143     *
144     * @param response An HttpResponse returned from the Vonage Voice API
145     *
146     * @return A ResultT type representing the result of the REST call
147     *
148     * @throws IOException if a problem occurs parsing the response
149     */
150    protected abstract ResultT parseResponse(HttpResponse response) throws IOException;
151}