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;
}
}
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
newInstance()
method with multiple creational methodsHere is an example of option 2:
public static RequestCloner<HttpPost> forPostRequest(String URL) {
return new PostRequestCloner(URL);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With