blob: 58d0e5b5c4e48d60fa3851bb9c461b825dfc5a4f [file] [log] [blame]
// Copyright (C) 2013 The Android Open Source Project
//
// 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 com.googlesource.gerrit.plugins.its.rtc.network;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
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.HttpRequestBase;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpParams;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.googlesource.gerrit.plugins.its.rtc.api.ResourceNotFoundException;
public class Transport {
public static final String APP_JSON = "application/json";
public static final String ANY = "*/*";
public static final String APP_OSLC =
"application/x-oslc-cm-change-request+json";
private static final Log log = LogFactory.getLog(Transport.class);
public static final ThreadLocal<String> etag = new ThreadLocal<String>();
protected HttpClient httpclient;
protected Gson gson;
protected String baseUrl;
private HttpParams httpParams;
private RTCClient rtcClient;
public Transport(RTCClient rtcClient, String baseUrl, DefaultHttpClient httpclient, HttpParams httpParams) {
this.rtcClient = rtcClient;
this.baseUrl = baseUrl;
this.httpclient = httpclient;
this.httpParams = httpParams;
}
public void setGson(Gson gson) {
this.gson = gson;
}
public <T> T put(final String path, final Type typeOrClass, JsonObject data,
String etag) throws IOException {
HttpPut request = new HttpPut(toUri(path));
if (log.isDebugEnabled())
log.debug("Preparing PUT against " + request.getURI() + " using etag "
+ etag + " and data " + data);
request.setEntity(new StringEntity(data.toString(), StandardCharsets.UTF_8));
if (etag != null) request.addHeader("If-Match", etag);
return invoke(request, typeOrClass, APP_OSLC, APP_OSLC);
}
public <T> T patch(final String path, final Type typeOrClass,
JsonObject data, String etag) throws IOException {
HttpPatch request = newHttpPatch(path);
if (log.isDebugEnabled())
log.debug("Preparing PATCH against " + request.getURI() + " using etag "
+ etag + " and data " + data);
request.setEntity(new StringEntity(data.toString(), StandardCharsets.UTF_8));
if (etag != null) request.addHeader("If-Match", etag);
return invoke(request, typeOrClass, APP_OSLC, APP_OSLC);
}
public <T> T post(final String path, final Type typeOrClass,
String contentType,
final NameValuePair... params) throws IOException {
HttpPost request = newHttpPost(path);
if (log.isDebugEnabled())
log.debug("Preparing POST against " + request.getURI() + " using params "
+ Arrays.asList(params));
List<NameValuePair> nameValuePairs = Arrays.asList(params);
request.setEntity(new UrlEncodedFormEntity(nameValuePairs, StandardCharsets.UTF_8));
return invoke(request, typeOrClass, contentType, null);
}
public <T> T get(final String path, final Type typeOrClass)
throws IOException, MalformedURLException {
final HttpGet request = newHttpGet(path);
if (log.isDebugEnabled())
log.debug("Preparing GET against " + request.getURI());
return invoke(request, typeOrClass, APP_JSON, null);
}
public String get(final String path) throws IOException,
MalformedURLException {
final HttpGet request = newHttpGet(path);
if (log.isDebugEnabled())
log.debug("Preparing GET against " + request.getURI());
return invoke(request, null, ANY, null);
}
@SuppressWarnings("unchecked")
private synchronized <T> T invoke(HttpRequestBase request,
Object typeOrClass, String acceptType, String contentType)
throws IOException, ClientProtocolException, ResourceNotFoundException {
if (contentType != null) {
request.addHeader("Content-Type", contentType);
}
if (acceptType != null) {
request.addHeader("Accept", acceptType);
}
HttpResponse response = httpclient.execute(request);
try {
final int code = response.getStatusLine().getStatusCode();
if (code / 100 != 2) {
if (code == 404) {
log.debug("API call failed: " + response.getStatusLine());
throw new ResourceNotFoundException(request.getURI());
} else {
log.debug("API call failed: " + response.getStatusLine());
throw new IOException("API call failed! " + response.getStatusLine());
}
}
String responseContentTypeString = getResponseContentType(response);
String entityString = readEntityAsString(response);
if (!assertValidContentType(acceptType, responseContentTypeString)) {
log.error("Request to " + request.getURI()
+ " failed because of an invalid content returned:\n"
+ entityString);
rtcClient.setLoggedIn(false);
throw new InvalidContentTypeException("Wrong content type '"
+ responseContentTypeString + "' in HTTP response (Expected: "
+ acceptType + ")");
}
if (typeOrClass != null && acceptType.endsWith("json")
&& responseContentTypeString.endsWith("json")) {
Transport.etag.set(extractEtag(response));
if (typeOrClass instanceof ParameterizedType) {
return gson.fromJson(entityString, (Type) typeOrClass);
} else {
return gson.fromJson(entityString, (Class<T>) typeOrClass);
}
} else if (typeOrClass != null && typeOrClass.equals(String.class)) {
return (T) entityString;
} else {
if (log.isDebugEnabled()) log.debug(entityString);
return null;
}
} finally {
consumeHttpEntity(response.getEntity());
Transport.etag.set(null);
}
}
private boolean assertValidContentType(String acceptType,
String responseContentTypeString) throws IOException {
if (acceptType == null) {
return true;
}
if (acceptType.endsWith("/*")) {
return true;
}
if (acceptType.split("/")[1].equalsIgnoreCase(responseContentTypeString
.split("/")[1])) {
return true;
}
return false;
}
public String getResponseContentType(HttpResponse response) {
Header contentType = response.getEntity().getContentType();
if (contentType == null) {
return null;
}
String contentTypeValue = contentType.getValue();
if (contentTypeValue == null) {
return null;
}
for (String contentTypeItem : contentTypeValue.split(";")) {
if (contentTypeItem.indexOf('/') >= 0) {
return contentTypeItem;
}
}
return null;
}
private String readEntityAsString(HttpResponse response)
throws IllegalStateException, IOException {
String charset = "utf-8";
Header[] contentTypes = response.getHeaders("Content-Type");
for (Header header : contentTypes) {
if (header.getName().equalsIgnoreCase("charset")) {
charset = header.getValue();
}
}
ByteArrayOutputStream responseOut = new ByteArrayOutputStream();
try {
IOUtils.copy(response.getEntity().getContent(), responseOut);
} finally {
responseOut.close();
}
return new String(responseOut.toByteArray(), Charset.forName(charset));
}
// We can't use the HTTP Client 4.2 EntityUtils.consume()
// because of compatibility issues with gerrit-pgm 2.5
// that includes httpclient 4.0
private void consumeHttpEntity(final HttpEntity entity) throws IOException {
if (entity == null) {
return;
}
if (entity.isStreaming()) {
InputStream instream = entity.getContent();
if (instream != null) {
instream.close();
}
}
}
private String extractEtag(HttpResponse response) {
final Header etagHeader = response.getFirstHeader("ETag");
return etagHeader == null ? null : etagHeader.getValue().substring(1,
etagHeader.getValue().length() - 1);
}
private HttpGet newHttpGet(final String path) throws MalformedURLException {
HttpGet get = new HttpGet(toUri(path));
get.setParams(httpParams);
return get;
}
private HttpPost newHttpPost(final String path) throws MalformedURLException {
return new HttpPost(toUri(path));
}
private HttpPatch newHttpPatch(final String path)
throws MalformedURLException {
return new HttpPatch(toUri(path));
}
private String toUri(final String path) throws MalformedURLException {
if (path.startsWith(baseUrl))
return path;
else
return baseUrl + path;
}
}