All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor Maven / Gradle / Ivy

There is a newer version: 1.1.1
Show newest version
package org.jboss.resteasy.client.core.executors;

import org.apache.commons.io.output.DeferredFileOutputStream;
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.entity.FileEntity;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.jboss.resteasy.client.ClientExecutor;
import org.jboss.resteasy.client.ClientRequest;
import org.jboss.resteasy.client.ClientResponse;
import org.jboss.resteasy.client.core.BaseClientResponse;
import org.jboss.resteasy.client.core.BaseClientResponse.BaseClientResponseStreamFactory;
import org.jboss.resteasy.client.core.SelfExpandingBufferredInputStream;
import org.jboss.resteasy.client.exception.mapper.ApacheHttpClient4ExceptionMapper;
import org.jboss.resteasy.client.exception.mapper.ClientExceptionMapper;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.resteasy.util.CaseInsensitiveMap;
import org.jboss.resteasy.util.Types;

import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriBuilder;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author Bill Burke
 * @version $Revision: 1 $
 */
public class ApacheHttpClient4Executor implements ClientExecutor
{
   public static final String BYTE_MEMORY_UNIT = "BY";
   public static final String KILOBYTE_MEMORY_UNIT = "KB";
   public static final String MEGABYTE_MEMORY_UNIT = "MB";
   public static final String GIGABYTE_MEMORY_UNIT = "GB";

   /**
    * Used to build temp file prefix.
    */
   private static String processId = null;

   static
   {
      ApacheHttpClient4Executor.processId = ManagementFactory.getRuntimeMXBean().getName().replaceAll("[^0-9a-zA-Z]", "");
   }

   static synchronized private void checkClientExceptionMapper()
   {
      if (ResteasyProviderFactory.getInstance().getClientExceptionMapper(Exception.class) == null)
      {
         Type exceptionType = Types.getActualTypeArgumentsOfAnInterface(ApacheHttpClient4ExceptionMapper.class, ClientExceptionMapper.class)[0];
         ResteasyProviderFactory.getInstance().addClientExceptionMapper(new ApacheHttpClient4ExceptionMapper(), exceptionType);
      }
   }

   protected HttpClient httpClient;
   protected boolean createdHttpClient;
   protected HttpContext httpContext;
   protected boolean closed;

   /**
    * For uploading File's over JAX-RS framework, this property, together with {@link #fileUploadMemoryUnit},
    * defines the maximum File size allowed in memory. If fileSize exceeds this size, it will be stored to
    * {@link #fileUploadTempFileDir}. 
*
* Defaults to 1 MB */ private int fileUploadInMemoryThresholdLimit = 1; /** * The unit for {@link #fileUploadInMemoryThresholdLimit}.
*
* Defaults to MB. * * @see MemoryUnit */ private MemoryUnit fileUploadMemoryUnit = MemoryUnit.MB; /** * Temp directory to write output request stream to. Any file to be uploaded has to be written out to the * output request stream to be sent to the service and when the File is too huge the output request stream is * written out to the disk rather than to memory.
*
* Defaults to JVM temp directory. */ private File fileUploadTempFileDir = new File(System.getProperty("java.io.tmpdir")); /** * Java Util Logger */ private final static Logger LOGGER = Logger.getLogger(ApacheHttpClient4Executor.class.getName()); public ApacheHttpClient4Executor() { this(new DefaultHttpClient(), null); this.createdHttpClient = true; } public ApacheHttpClient4Executor(HttpClient httpClient) { this(httpClient, null); } public ApacheHttpClient4Executor(HttpClient httpClient, HttpContext httpContext) { this.httpClient = httpClient; this.httpContext = httpContext; checkClientExceptionMapper(); } public HttpClient getHttpClient() { return httpClient; } public HttpContext getHttpContext() { return httpContext; } public void setHttpContext(HttpContext httpContext) { this.httpContext = httpContext; } public static CaseInsensitiveMap extractHeaders( HttpResponse response) { final CaseInsensitiveMap headers = new CaseInsensitiveMap(); for (Header header : response.getAllHeaders()) { headers.add(header.getName(), header.getValue()); } return headers; } public ClientRequest createRequest(String uriTemplate) { return new ClientRequest(uriTemplate, this); } public ClientRequest createRequest(UriBuilder uriBuilder) { return new ClientRequest(uriBuilder, this); } static class ResponseStream extends SelfExpandingBufferredInputStream { BaseClientResponse response; public ResponseStream(InputStream in, BaseClientResponse response) { super(in); // Keep a reference to the response object to prevent it being finalized prematurely this.response = response; } public synchronized void close() throws IOException { super.close(); // Response object is no longer needed and can be finalized response = null; } } @SuppressWarnings("unchecked") public ClientResponse execute(ClientRequest request) throws Exception { String uri = request.getUri(); final HttpRequestBase httpMethod = createHttpMethod(uri, request.getHttpMethod()); try { loadHttpMethod(request, httpMethod); final HttpResponse res = httpClient.execute(httpMethod, httpContext); final BaseClientResponse response = new BaseClientResponse(null, this); BaseClientResponseStreamFactory sf = new BaseClientResponseStreamFactory() { InputStream stream; public InputStream getInputStream() throws IOException { if (stream == null) { HttpEntity entity = res.getEntity(); if (entity == null) return null; stream = new SelfExpandingBufferredInputStream(entity.getContent()); } return 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 (stream != null) { stream.close(); } else { InputStream is = getInputStream(); if (is != null) { is.close(); } } } catch (Exception ignore) { } } }; response.setStreamFactory(sf); response.setAttributes(request.getAttributes()); response.setStatus(res.getStatusLine().getStatusCode()); response.setHeaders(extractHeaders(res)); response.setProviderFactory(request.getProviderFactory()); return response; } finally { cleanUpAfterExecute(httpMethod); } } /** * 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. * @see #writeRequestBodyToOutputStream(ClientRequest) */ 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); } } } } 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 formparams = new ArrayList(); for (Map.Entry> formParam : request.getFormParameters().entrySet()) { List 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."); 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); } } /** * 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 {@link #writeRequestBodyToOutputStream(ClientRequest)}. * * @param request - * @return - the built HttpEntity * @throws IOException - */ protected HttpEntity buildEntity(final ClientRequest 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.getBodyContentType().toString())); entityToBuild = entityToBuildByteArray; } else { File requestBodyFile = memoryManagedOutStream.getFile(); requestBodyFile.deleteOnExit(); entityToBuild = new FileExposingFileEntity(memoryManagedOutStream.getFile(), request.getBodyContentType().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 ClientRequest request) throws IOException { DeferredFileOutputStream memoryManagedOutStream = new DeferredFileOutputStream(this.fileUploadInMemoryThresholdLimit * getMemoryUnitMultiplier(), getTempfilePrefix(), ".tmp", this.fileUploadTempFileDir); request.writeRequestBody(request.getHeadersAsObjects(), memoryManagedOutStream); 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; } /** * Use context information, which will include node name, to avoid conflicts in case of multiple VMS using same * temp directory location. * * @return - */ protected String getTempfilePrefix() { return ApacheHttpClient4Executor.processId; } /** * 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) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "Could not delete file' " + tempRequestFile.getAbsolutePath() + "' for request: ", ex); } } /** * Setter for the {@link HttpClient} to which this class delegates the actual HTTP call. Note that this class * acts as the adapter between RestEasy and Apache HTTP Component library. * * @param pHttpClient - */ void setHttpClient(HttpClient pHttpClient) { this.httpClient = pHttpClient; } /** * Setter for {@link #fileUploadInMemoryThresholdLimit} * * @param pInMemoryThresholdLimit - the inMemoryThresholdLimitMB to set */ public void setFileUploadInMemoryThresholdLimit(int pInMemoryThresholdLimit) { this.fileUploadInMemoryThresholdLimit = pInMemoryThresholdLimit; } /** * Setter for {@link #fileUploadTempFileDir} * * @param pTempFileDir the tempFileDir to set */ public void setFileUploadTempFileDir(File pTempFileDir) { this.fileUploadTempFileDir = pTempFileDir; } /** * Setter for {@link #fileUploadMemoryUnit} * * @param pMemoryUnit the memoryUnit to set */ public void setFileUploadMemoryUnit(String pMemoryUnit) { this.fileUploadMemoryUnit = MemoryUnit.valueOf(pMemoryUnit); } public void commitHeaders(ClientRequest request, HttpRequestBase httpMethod) { MultivaluedMap headers = request.getHeaders(); 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); } } } public void close() { if (closed) return; if (createdHttpClient && httpClient != null) { ClientConnectionManager manager = httpClient.getConnectionManager(); if (manager != null) { manager.shutdown(); } } closed = true; } public boolean isClosed() { return closed; } public void finalize() throws Throwable { close(); super.finalize(); } /** * We use {@link 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 * {@link RestCFHttpClientExecutor#writeRequestBodyToOutputStream(ClientRequest)}); 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 ApacheHttpClient4Executor} ) 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, pContentType); } /** * @return - the content File enclosed by this FileEntity. */ File getFile() { return this.file; } } /** * Enumeration to represent memory units. */ private static enum MemoryUnit { /** * Bytes */ BY, /** * Killo Bytes */ KB, /** * Mega Bytes */ MB, /** * Giga Bytes */ GB } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy