/**
* Copyright 2014 Opower, Inc.
* 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.opower.rest.client.generator.executors;
import com.google.common.collect.ImmutableList;
import com.opower.rest.client.generator.core.BaseClientResponse;
import com.opower.rest.client.generator.core.ClientRequest;
import com.opower.rest.client.generator.core.ClientRequestFilter;
import com.opower.rest.client.generator.core.ClientResponse;
import com.opower.rest.client.generator.core.SelfExpandingBufferredInputStream;
import com.opower.rest.client.generator.util.CaseInsensitiveMap;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.ws.rs.core.MultivaluedMap;
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.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.HttpRequestBase;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* ClientExecutor implementation that uses HttpClient 4.
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
*
*/
public class ApacheHttpClient4Executor extends AbstractClientExecutor {
protected final HttpClient httpClient;
protected boolean createdHttpClient;
protected HttpContext httpContext;
protected boolean closed;
/**
* Create an instance using the DefaultHttpClient.
*/
public ApacheHttpClient4Executor() {
this(new DefaultHttpClient(new PoolingClientConnectionManager()), ImmutableList.<ClientRequestFilter>of());
}
public ApacheHttpClient4Executor(List<ClientRequestFilter> requestFilters ) {
this(new DefaultHttpClient(new PoolingClientConnectionManager()), requestFilters);
}
/**
* Create an instance using the specified HttpClient.
* @param httpClient the HttpClient to use
*/
public ApacheHttpClient4Executor(HttpClient httpClient) {
this(httpClient, ImmutableList.<ClientRequestFilter>of());
}
public ApacheHttpClient4Executor(HttpClient httpClient, List<ClientRequestFilter> requestFilters) {
super(requestFilters);
this.httpClient = checkNotNull(httpClient);
}
/**
* Extracts the headers from the given HttpResponse.
* @param response the HttpResponse to get the headers from
* @return A map of the headers found on the HttpResponse
*/
public static CaseInsensitiveMap<String> extractHeaders(
HttpResponse response) {
final CaseInsensitiveMap<String> headers = new CaseInsensitiveMap<String>();
for (Header header : response.getAllHeaders()) {
headers.add(header.getName(), header.getValue());
}
return headers;
}
@Override
@SuppressWarnings("unchecked")
public ClientResponse execute(ClientRequest request) throws Exception {
String uri = request.getUri();
final HttpRequestBase httpMethod = createHttpMethod(uri, request.getHttpMethod());
loadHttpMethod(request, httpMethod);
final HttpResponse res = this.httpClient.execute(httpMethod, this.httpContext);
BaseClientResponse response = new BaseClientResponse(new SimpleBaseClientResponseStreamFactory(res), this,
request.getErrorStatusCriteria());
response.setStatus(res.getStatusLine().getStatusCode());
response.setHeaders(extractHeaders(res));
response.setProviders(request.getProviders());
return response;
}
private HttpRequestBase createHttpMethod(String url, String restVerb) {
if ("GET".equals(restVerb)) {
return new HttpGet(url);
} else if ("POST".equals(restVerb)) {
return new HttpPost(url);
} else {
final String verb = restVerb;
return new HttpPost(url) {
@Override
public String getMethod() {
return verb;
}
};
}
}
public void loadHttpMethod(final ClientRequest request, HttpRequestBase httpMethod) throws Exception {
if (httpMethod instanceof HttpGet && request.followRedirects()) {
HttpClientParams.setRedirecting(httpMethod.getParams(), true);
} else {
HttpClientParams.setRedirecting(httpMethod.getParams(), false);
}
if (request.getBody() != null && !request.getFormParameters().isEmpty())
throw new RuntimeException("You cannot send both form parameters and an entity body");
if (!request.getFormParameters().isEmpty()) {
commitHeaders(request, httpMethod);
HttpPost post = (HttpPost) httpMethod;
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
for (Map.Entry<String, List<String>> formParam : request.getFormParameters().entrySet()) {
List<String> values = formParam.getValue();
for (String value : values) {
formparams.add(new BasicNameValuePair(formParam.getKey(), value));
}
}
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(entity);
} else if (request.getBody() != null) {
if (httpMethod instanceof HttpGet) throw new RuntimeException("A GET request cannot have a body.");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
request.writeRequestBody(request.getHeadersAsObjects(), baos);
ByteArrayEntity entity = new ByteArrayEntity(baos.toByteArray()) {
@Override
public Header getContentType() {
return new BasicHeader("Content-Type", request.getBodyContentType().toString());
}
};
HttpPost post = (HttpPost) httpMethod;
commitHeaders(request, httpMethod);
post.setEntity(entity);
} catch (IOException e) {
throw new RuntimeException(e);
}
} else // no body
{
commitHeaders(request, httpMethod);
}
}
public void commitHeaders(ClientRequest request, HttpRequestBase httpMethod) {
MultivaluedMap<String, String> headers = request.getHeaders();
for (Map.Entry<String, List<String>> header : headers.entrySet()) {
List<String> values = header.getValue();
for (String value : values) {
// System.out.println(String.format("setting %s = %s", header.getKey(), value));
httpMethod.addHeader(header.getKey(), value);
}
}
}
@Override
public void close() {
if (closed)
return;
if (createdHttpClient && httpClient != null) {
ClientConnectionManager manager = httpClient.getConnectionManager();
if (manager != null) {
manager.shutdown();
}
}
closed = true;
}
//CHECKSTYLE:OFF
@Override
public void finalize() throws Throwable {
close();
super.finalize();
}//CHECKSTYLE:ON
private class SimpleBaseClientResponseStreamFactory implements BaseClientResponse.BaseClientResponseStreamFactory {
private final HttpResponse res;
private InputStream stream;
private SimpleBaseClientResponseStreamFactory(HttpResponse res) {
this.res = res;
}
public InputStream getInputStream() throws IOException {
if (this.stream == null) {
HttpEntity entity = this.res.getEntity();
if (entity == null) { return null; }
this.stream = new SelfExpandingBufferredInputStream(entity.getContent());
}
return this.stream;
}
public void performReleaseConnection() {
// Apache Client 4 is stupid, You have to get the InputStream and close it if there is an entity
// otherwise the connection is never released. There is, of course, no close() method on response
// to make this easier.
try {
if (this.stream != null) {
this.stream.close();
} else {
InputStream is = getInputStream();
if (is != null) { is.close(); }
}
} catch (Exception ignore) { }
}
}
}