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}