com.cerner.beadledom.client.resteasy.ApacheHttpClient4Dot3Engine Maven / Gradle / Ivy
package com.cerner.beadledom.client.resteasy;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.core.MultivaluedMap;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.DeferredFileOutputStream;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
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.protocol.HttpClientContext;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine;
import org.jboss.resteasy.client.jaxrs.internal.ClientInvocation;
import org.jboss.resteasy.client.jaxrs.internal.ClientResponse;
import org.jboss.resteasy.logging.Logger;
/**
* This client is extended from the {@link ApacheHttpClient4Engine} to provide support for adding
* request configuration information to each HTTP request.
*
* Note: Most of the contents of this class are copied from
* {@link ApacheHttpClient4Engine}.
*
* @author John Leacox
* @author Sundeep Paruvu
* @since 1.0
* @see ApacheHttpClient4Engine
*/
@SuppressWarnings(value = "")
class ApacheHttpClient4Dot3Engine extends ApacheHttpClient4Engine {
private static final Logger logger = Logger.getLogger(ApacheHttpClient4Dot3Engine.class);
public ApacheHttpClient4Dot3Engine(HttpClient httpClient, HttpContext httpContext) {
super(httpClient, httpContext);
this.httpClient = httpClient;
this.httpContext = httpContext;
}
@SuppressWarnings("unchecked")
@Override
public ClientResponse invoke(ClientInvocation request) {
String uri = request.getUri().toString();
final HttpRequestBase httpMethod = createHttpMethod(uri, request.getMethod());
final HttpResponse res;
try {
loadHttpMethod(request, httpMethod);
httpMethod.setConfig((RequestConfig) httpContext.getAttribute(HttpClientContext.REQUEST_CONFIG));
res = httpClient.execute(httpMethod, httpContext);
} catch (Exception e) {
throw new ProcessingException("Unable to invoke request", e);
} finally {
cleanUpAfterExecute(httpMethod);
}
ClientResponse response = new ClientResponse(request.getClientConfiguration()) {
InputStream stream;
InputStream hc4Stream;
@Override
protected void setInputStream(InputStream is) {
stream = is;
}
public InputStream getInputStream() {
if (stream == null) {
HttpEntity entity = res.getEntity();
if (entity == null) {
return null;
}
try {
hc4Stream = entity.getContent();
stream = createBufferedStream(hc4Stream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return stream;
}
public void releaseConnection() throws IOException {
// 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 {
// Another stupid thing...TCK is testing a specific exception from stream.close()
// so, we let it propagate up.
if (stream != null) {
stream.close();
} else {
InputStream is = getInputStream();
if (is != null) {
is.close();
}
}
} finally {
// just in case the input stream was entirely replaced and not wrapped, we need
// to close the apache client input stream.
if (hc4Stream != null) {
try {
hc4Stream.close();
} catch (IOException ignored) {
}
} else {
try {
HttpEntity entity = res.getEntity();
if (entity != null) {
entity.getContent().close();
}
} catch (IOException ignored) {
}
}
}
}
};
response.setProperties(request.getMutableProperties());
response.setStatus(res.getStatusLine().getStatusCode());
response.setHeaders(extractHeaders(res));
response.setClientConfiguration(request.getClientConfiguration());
return response;
}
protected 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;
}
};
}
}
protected void loadHttpMethod(final ClientInvocation request, HttpRequestBase httpMethod)
throws Exception {
if (request.getEntity() != null) {
if (httpMethod instanceof HttpGet) {
throw new ProcessingException("A GET request cannot have a body.");
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
request.getDelegatingOutputStream().setDelegate(baos);
try {
HttpEntity entity = buildEntity(request);
HttpPost post = (HttpPost) httpMethod;
commitHeaders(request, httpMethod);
post.setEntity(entity);
} catch (IOException e) {
throw new RuntimeException(e);
}
} else // no body
{
commitHeaders(request, httpMethod);
}
}
protected void commitHeaders(ClientInvocation request, HttpRequestBase httpMethod) {
MultivaluedMap headers = request.getHeaders().asMap();
for (Map.Entry> header : headers.entrySet()) {
List 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 (CloseableHttpClient.class.isAssignableFrom(httpClient.getClass())) {
IOUtils.closeQuietly((CloseableHttpClient) httpClient);
}
closed = true;
}
public void finalize() throws Throwable {
close();
super.finalize();
}
/**
* If passed httpMethod is of type HttpPost then obtain its entity. If the entity has an enclosing File then
* delete it by invoking this method after the request has completed. The entity will have an enclosing File
* only if it was too huge to fit into memory.
*
* @param httpMethod - the httpMethod to clean up.
*/
protected void cleanUpAfterExecute(final HttpRequestBase httpMethod) {
if (httpMethod != null && httpMethod instanceof HttpPost) {
HttpPost postMethod = (HttpPost) httpMethod;
HttpEntity entity = postMethod.getEntity();
if (entity != null && entity instanceof FileExposingFileEntity) {
File tempRequestFile = ((FileExposingFileEntity) entity).getFile();
try {
boolean isDeleted = tempRequestFile.delete();
if (!isDeleted) {
handleFileNotDeletedError(tempRequestFile, null);
}
} catch (Exception ex) {
handleFileNotDeletedError(tempRequestFile, ex);
}
}
}
}
/**
* Build the HttpEntity to be sent to the Service as part of (POST) request. Creates a off-memory
* {@link FileExposingFileEntity} or a regular in-memory {@link ByteArrayEntity} depending on if the request
* OutputStream fit into memory when built by calling.
*
* @param request -
* @return - the built HttpEntity
* @throws IOException -
*/
protected HttpEntity buildEntity(final ClientInvocation request) throws IOException {
HttpEntity entityToBuild = null;
DeferredFileOutputStream memoryManagedOutStream = writeRequestBodyToOutputStream(request);
if (memoryManagedOutStream.isInMemory()) {
ByteArrayEntity entityToBuildByteArray =
new ByteArrayEntity(memoryManagedOutStream.getData());
entityToBuildByteArray.setContentType(
new BasicHeader(HTTP.CONTENT_TYPE, request.getHeaders().getMediaType().toString()));
entityToBuild = entityToBuildByteArray;
} else {
File requestBodyFile = memoryManagedOutStream.getFile();
requestBodyFile.deleteOnExit();
entityToBuild = new FileExposingFileEntity(
memoryManagedOutStream.getFile(), request.getHeaders().getMediaType().toString());
}
return entityToBuild;
}
/**
* Creates the request OutputStream, to be sent to the end Service invoked, as a
* DeferredFileOutputStream.
*
*
* @param request -
* @return - DeferredFileOutputStream with the ClientRequest written out per HTTP specification.
* @throws IOException -
*/
private DeferredFileOutputStream writeRequestBodyToOutputStream(final ClientInvocation request)
throws IOException {
DeferredFileOutputStream memoryManagedOutStream =
new DeferredFileOutputStream(
this.fileUploadInMemoryThresholdLimit * getMemoryUnitMultiplier(),
getTempfilePrefix(), ".tmp", this.fileUploadTempFileDir);
request.getDelegatingOutputStream().setDelegate(memoryManagedOutStream);
request.writeRequestBody(request.getEntityStream());
memoryManagedOutStream.close();
return memoryManagedOutStream;
}
/**
* @return - the constant to multiply {@link #fileUploadInMemoryThresholdLimit} with based on
* {@link #fileUploadMemoryUnit} enumeration value.
*/
private int getMemoryUnitMultiplier() {
switch (this.fileUploadMemoryUnit) {
case BY:
return 1;
case KB:
return 1024;
case MB:
return 1024 * 1024;
case GB:
return 1024 * 1024 * 1024;
}
return 1;
}
/**
* Log that the file did not get deleted but prevent the request from failing by eating the exception. The file
* has been registered to delete on exit, so it will get deleted eventually.
*
* @param tempRequestFile -
* @param ex - a null may be passed in which case this param gets ignored.
*/
private void handleFileNotDeletedError(File tempRequestFile, Exception ex) {
logger.warn(
"Could not delete file' " + tempRequestFile.getAbsolutePath() + "' for request: ", ex);
}
/**
* We use {@link org.apache.http.entity.FileEntity} as the {@link HttpEntity} implementation when the request OutputStream has been
* saved to a File on disk (because it was too large to fit into memory see however, we have to delete
* the File supporting the FileEntity
, otherwise the disk will soon run out of space - remember
* that there can be very huge files, in GB range, processed on a regular basis - and FileEntity exposes its
* content File as a protected field. For the enclosing parent class ( {@link ApacheHttpClient4Dot3Engine} ) to be
* able to get a handle to this content File and delete it, this class expose the content File.
* This class is private scoped to prevent access to this content File outside of the parent class.
*
* @author Sandeep Tikoo
*/
private static class FileExposingFileEntity extends FileEntity {
/**
* @param pFile -
* @param pContentType -
*/
public FileExposingFileEntity(File pFile, String pContentType) {
super(pFile, ContentType.create(pContentType));
}
/**
* @return - the content File enclosed by this FileEntity.
*/
File getFile() {
return this.file;
}
}
}