Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bounded wildcard in return type of static factory pattern

I've read in Effective Java that you should not use bounded wildcards as return types, but I don't know how should I do this then. The only way my code compiles is by using RequestCloner<? extends HttpUriRequest> as return type in the static factory. Am I doing something wrong or is there a workaround?

NOTE: One thing to be noted is that HttpUriRequest has the method setHeader, but only HttpPost has the method setEntity.

abstract class RequestCloner<T extends HttpUriRequest> {

  protected T clonedRequest;

  private enum RequestType {
    GET, POST, DELETE
  }

  static RequestCloner<? extends HttpUriRequest> newInstance(
      String type, String url) {
    RequestType requestType = RequestType.valueOf(type);
    switch (requestType) {
    case GET:
      return new GetRequestCloner(url);
    case POST:
      return new PostRequestCloner(url);
    case DELETE:
      return new DeleteRequestCloner(url);
    default:
      throw new IllegalArgumentException(String.format(
          "Method '%s' not supported",
          type));
    }
  }

  public abstract HttpUriRequest clone(HttpServletRequest servletRequest) throws IOException;

  protected void cloneHeaders(HttpServletRequest servletRequest) {
    @SuppressWarnings("unchecked")
    Enumeration<String> e = servletRequest.getHeaderNames();
    while (e.hasMoreElements()) {
        String header = e.nextElement();
        if (!header.equalsIgnoreCase("Content-Length")
                && !header.equalsIgnoreCase("Authorization")
                && !header.equalsIgnoreCase("Host")) {
            clonedRequest.setHeader(new BasicHeader(header, servletRequest.getHeader(header)));
        }
    }
  }
}

...

class GetRequestCloner extends RequestCloner<HttpGet> {

  GetRequestCloner(String url) {
    this.clonedRequest = new HttpGet(url);
  }

  @Override
  public HttpUriRequest clone(HttpServletRequest servletRequest) {
    cloneHeaders(servletRequest);
    return clonedRequest;
  }
}

...

class PostRequestCloner extends RequestCloner<HttpPost> {

  private static final int MAX_STR_LEN = 1024;

  PostRequestCloner(String url) {
    this.clonedRequest = new HttpPost(url);
  }

  @Override
  public HttpUriRequest clone(HttpServletRequest servletRequest) throws IOException {
    cloneHeaders(servletRequest);
    cloneBody(servletRequest);
    return clonedRequest;
  }

  private void cloneBody(HttpServletRequest servletRequest) throws IOException {
    StringBuilder sb = new StringBuilder("");
    BufferedReader br = new BufferedReader(new InputStreamReader(
            servletRequest.getInputStream(),
            "UTF-8"));
    String line = "";
    while ((line = br.readLine()) != null && sb.length() < MAX_STR_LEN) {
        sb.append(line);
    }
    br.close();
    clonedRequest.setEntity(new StringEntity(sb.toString(), "UTF-8"));
  }
}

...

class DeleteRequestCloner extends RequestCloner<HttpDelete> {

  DeleteRequestCloner(String url) {
    this.clonedRequest = new HttpDelete(url);
  }

  @Override
  public HttpUriRequest clone(HttpServletRequest servletRequest) {
    cloneHeaders(servletRequest);
    return clonedRequest;
  }
}
like image 613
carcaret Avatar asked Nov 22 '22 10:11

carcaret


1 Answers

Looking at your code, your class does not need to be generic. Looking further, there is the odd issue where the caller passes in a URL to create a cloner, then passes a HttpServletRequest (which could in theory be a different type of request) into the clone method.

I can see two kinds of solutions, depending on whether you really need RequestCloner to be generic.

If RequestCloner Does Not Need To Be Generic

Change the base class as follows:

abstract class RequestCloner {

  private enum RequestType {
    GET, POST, DELETE
  }

  public static HttpUriRequest cloneRequest(HttpServletRequest servletRequest)
        throws IOException {
    RequestCloner cloner = createCloner(servletRequest);
    String uri = servletRequest.getRequestURI();
    return cloner.clone(uri, servletRequest);
  }

  private static RequestCloner createCloner(HttpServletRequest servletRequest) {
    RequestType requestType = RequestType.valueOf(servletRequest. getMethod());
    switch (requestType) {
    case GET:
      return new GetRequestCloner();
    case POST:
      return new PostRequestCloner();
    case DELETE:
      return new DeleteRequestCloner();
    default:
      throw new AssertionFailedError(String.format(
          "RequestType '%s' not supported", requestType));
    }
  }

  protected abstract HttpUriRequest clone(
      String uri, HttpServletRequest servletRequest)
      throws IOException;

  protected final void cloneHeaders(
      HttpServletRequest servletRequest,
      HttpUriRequest clonedRequest) { // note addition of parameter
    // same code as before, but modify the passed-in clonedRequest
  }
}

Subclasses of RequestCloner would override clone(), optionally changing the return value to return a subclass of HttpUriRequest:

class PostRequestCloner extends RequestCloner {
  private static final int MAX_STR_LEN = 1024;

  @Override
  protected HttpPost clone(
      String uri, HttpServletRequest servletRequest)
      throws IOException {
    HttpPost clonedRequest = new HttpPost(uri);
    cloneHeaders(servletRequest, clonedRequest);
    cloneBody(servletRequest, clonedRequest);
    return clonedRequest;
  }

  ...
}

The disadvantages of the above solution is the return value of cloneRequest() is the same for a GET request as a POST request.

If you prefer, you can remove the switch by adding code to the enum:

abstract class RequestCloner {

  private enum RequestType {
    GET(new GetRequestCloner()),
    POST(new PostRequestCloner()),
    DELETE(new DeleteRequestCLoner());

    private final RequestCloner requestCloner;

    private RequestType(RequestCloner requestCloner) {
      this.requestCloner = requestCloner();
    }
  }

  public static HttpUriRequest cloneRequest(HttpServletRequest servletRequest)
        throws IOException {
    RequestType requestType = RequestType.valueOf(servletRequest. getMethod());
    String uri = servletRequest.getRequestURI();
    return requestType.requestCloner.clone(uri, servletRequest);
  }

  ...
}

If you want the return value to depend on the type of request, then the caller will need to either specify some kind of type token, explicitly reference the subclass of RequestCloner, or add one static method to RequestCloner for each type of request.

If RequestCloner Needs To Be Generic

Given the code in the question, the only benefit of making RequestCloner generic is to make the return value of clone() different for GET or POST.

To do that, you have two options

  1. Making the subclasses (and their constructors) public.
  2. Replace your newInstance() method with multiple creational methods

Here is an example of option 2:

public static RequestCloner<HttpPost> forPostRequest(String URL) {
  return new PostRequestCloner(URL);
}
like image 73
NamshubWriter Avatar answered Dec 15 '22 23:12

NamshubWriter