/*
* Copyright 2015 LinkedIn Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package azkaban.utils;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import org.apache.http.HttpEntity;
import org.apache.http.HttpMessage;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.log4j.Logger;
/** class handles the communication between the application and
* a Restful API based web server.
* @param T : type of the returning response object.
* Note: the idea of this abstract class is to provide a wrapper for the logic around HTTP layer communication so
* development work can take this as a black box and focus on processing the result.
* With that said the abstract class will be provided as a template, which ideally can support different types
* of returning object (Dictionary, xmlDoc , text etc.)
* */
public abstract class RestfulApiClient<T> {
protected static Logger logger = Logger.getLogger(RestfulApiClient.class);
/** Method to transform the response returned by the httpClient into the
* type specified.
* Note: Method need to handle case such as failed request.
* Also method is not supposed to pass the response object out
* via the returning value as the response will be closed after the
* execution steps out of the method context.
* @throws HttpResponseException
* @throws IOException
* @throws ParseException
* **/
protected abstract T parseResponse(HttpResponse response)
throws HttpResponseException, IOException;
/** function to perform a Get http request.
* @param uri the URI of the request.
* @param headerEntries extra entries to be added to request header.
* @return the response object type of which is specified by user.
* @throws IOException */
public T httpGet(URI uri, List<NameValuePair> headerEntries) throws IOException{
// shortcut if the passed url is invalid.
if (null == uri){
logger.error(" unable to perform httpGet as the passed uri is null");
return null;
}
HttpGet get = new HttpGet(uri);
return this.sendAndReturn((HttpGet)completeRequest(get, headerEntries));
}
/** function to perform a Post http request.
* @param uri the URI of the request.
* @param headerEntries extra entries to be added to request header.
* @param postingBody the content to be posted , optional.
* @return the response object type of which is specified by user.
* @throws UnsupportedEncodingException, IOException */
public T httpPost(URI uri,
List<NameValuePair> headerEntries,
String postingBody) throws UnsupportedEncodingException, IOException{
// shortcut if the passed url is invalid.
if (null == uri){
logger.error(" unable to perform httpPost as the passed uri is null.");
return null;
}
HttpPost post = new HttpPost(uri);
return this.sendAndReturn(completeRequest(post,headerEntries,postingBody));
}
/** function to perform a Delete http request.
* @param uri the URI of the request.
* @param headerEntries extra entries to be added to request header.
* @return the response object type of which is specified by user.
* @throws IOException */
public T httpDelete(URI uri, List<NameValuePair> headerEntries) throws IOException{
// shortcut if the passed url is invalid.
if (null == uri){
logger.error(" unable to perform httpDelete as the passed uri is null.");
return null;
}
HttpDelete delete = new HttpDelete(uri);
return this.sendAndReturn((HttpDelete)completeRequest(delete, headerEntries));
}
/** function to perform a Put http request.
* @param uri the URI of the request.
* @param headerEntries extra entries to be added to request header.
* @param postingBody the content to be posted , optional.
* @return the response object type of which is specified by user.
* @throws UnsupportedEncodingException, IOException */
public T httpPut(URI uri, List<NameValuePair> headerEntries,
String postingBody) throws UnsupportedEncodingException, IOException{
// shortcut if the passed url is invalid.
if (null == uri){
logger.error(" unable to perform httpPut as the passed url is null or empty.");
return null;
}
HttpPut put = new HttpPut(uri);
return this.sendAndReturn(completeRequest(put, headerEntries, postingBody));
}
/** function to dispatch the request and pass back the response.
* */
protected T sendAndReturn(HttpUriRequest request) throws IOException{
CloseableHttpClient client = HttpClients.createDefault();
try {
return this.parseResponse(client.execute(request));
}finally{
client.close();
}
}
/** helper function to build a valid URI.
* @param host host name.
* @param port host port.
* @param path extra path after host.
* @param isHttp indicates if whether Http or HTTPS should be used.
* @param params extra query parameters.
* @return the URI built from the inputs.
* @throws IOException
* */
public static URI buildUri(String host, int port, String path,
boolean isHttp, Pair<String, String>... params) throws IOException{
URIBuilder builder = new URIBuilder();
builder.setScheme(isHttp? "http" : "https").setHost(host).setPort(port);
if (null != path && path.length() > 0){
builder.setPath(path);
}
if (params != null) {
for (Pair<String, String> pair : params) {
builder.setParameter(pair.getFirst(), pair.getSecond());
}
}
URI uri = null;
try {
uri = builder.build();
} catch (URISyntaxException e) {
throw new IOException(e);
}
return uri;
}
/** helper function to build a valid URI.
* @param uri the URI to start with.
* @param params extra query parameters to append.
* @return the URI built from the inputs.
* @throws IOException
* */
public static URI BuildUri(URI uri, Pair<String, String>... params) throws IOException{
URIBuilder builder = new URIBuilder(uri);
if (params != null) {
for (Pair<String, String> pair : params) {
builder.setParameter(pair.getFirst(), pair.getSecond());
}
}
URI returningUri = null;
try {
returningUri = builder.build();
} catch (URISyntaxException e) {
throw new IOException(e);
}
return returningUri;
}
/** helper function to fill the request with header entries .
* */
private static HttpMessage completeRequest(HttpMessage request,
List<NameValuePair> headerEntries){
if (null == request){
logger.error("unable to complete request as the passed request object is null");
return request;
}
// dump all the header entries to the request.
if (null != headerEntries && headerEntries.size() > 0){
for (NameValuePair pair : headerEntries){
request.addHeader(pair.getName(), pair.getValue());
}
}
return request;
}
/** helper function to fill the request with header entries and posting body .
* */
private static HttpEntityEnclosingRequestBase completeRequest(HttpEntityEnclosingRequestBase request,
List<NameValuePair> headerEntries,
String postingBody) throws UnsupportedEncodingException{
if (null != completeRequest(request, headerEntries)){
// dump the post body UTF-8 will be used as the default encoding type.
if (null != postingBody && postingBody.length() > 0){
HttpEntity entity = new ByteArrayEntity(postingBody.getBytes("UTF-8"));
request.setHeader("Content-Length", Long.toString(entity.getContentLength()));
request.setEntity(entity);
}
}
return request;
}
}