diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt index 51bbbff20..3e718c393 100644 --- a/RELEASE_NOTES.txt +++ b/RELEASE_NOTES.txt @@ -1,6 +1,9 @@ Changes since 4.0 Alpha 4 ------------------- +* [HTTPCLIENT-719] Clone support for HTTP request and cookie objects. + Contributed by Oleg Kalnichevski + * [HTTPCLIENT-776] Fixed concurrency issues with AbstractPoolEntry. Contributed by Sam Berlin diff --git a/module-client/src/main/java/org/apache/http/client/methods/HttpEntityEnclosingRequestBase.java b/module-client/src/main/java/org/apache/http/client/methods/HttpEntityEnclosingRequestBase.java index 79be0cf0c..65e2957f6 100644 --- a/module-client/src/main/java/org/apache/http/client/methods/HttpEntityEnclosingRequestBase.java +++ b/module-client/src/main/java/org/apache/http/client/methods/HttpEntityEnclosingRequestBase.java @@ -34,6 +34,7 @@ package org.apache.http.client.methods; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.client.utils.CloneUtils; import org.apache.http.protocol.HTTP; /** @@ -66,5 +67,15 @@ abstract class HttpEntityEnclosingRequestBase Header expect = getFirstHeader(HTTP.EXPECT_DIRECTIVE); return expect != null && HTTP.EXPECT_CONTINUE.equalsIgnoreCase(expect.getValue()); } - + + @Override + public Object clone() throws CloneNotSupportedException { + HttpEntityEnclosingRequestBase clone = + (HttpEntityEnclosingRequestBase) super.clone(); + if (this.entity != null) { + clone.entity = (HttpEntity) CloneUtils.clone(this.entity); + } + return clone; + } + } diff --git a/module-client/src/main/java/org/apache/http/client/methods/HttpRequestBase.java b/module-client/src/main/java/org/apache/http/client/methods/HttpRequestBase.java index b4e360330..710118f30 100644 --- a/module-client/src/main/java/org/apache/http/client/methods/HttpRequestBase.java +++ b/module-client/src/main/java/org/apache/http/client/methods/HttpRequestBase.java @@ -38,10 +38,13 @@ import java.util.concurrent.locks.ReentrantLock; import org.apache.http.ProtocolVersion; import org.apache.http.RequestLine; +import org.apache.http.client.utils.CloneUtils; import org.apache.http.conn.ClientConnectionRequest; import org.apache.http.conn.ConnectionReleaseTrigger; import org.apache.http.message.AbstractHttpMessage; import org.apache.http.message.BasicRequestLine; +import org.apache.http.message.HeaderGroup; +import org.apache.http.params.HttpParams; import org.apache.http.params.HttpProtocolParams; /** @@ -54,9 +57,9 @@ import org.apache.http.params.HttpProtocolParams; * @since 4.0 */ abstract class HttpRequestBase extends AbstractHttpMessage - implements HttpUriRequest, AbortableHttpRequest { + implements HttpUriRequest, AbortableHttpRequest, Cloneable { - private final Lock abortLock; + private Lock abortLock; private boolean aborted; @@ -159,5 +162,21 @@ abstract class HttpRequestBase extends AbstractHttpMessage } } } + + public boolean isAborted() { + return this.aborted; + } + @Override + public Object clone() throws CloneNotSupportedException { + HttpRequestBase clone = (HttpRequestBase) super.clone(); + clone.abortLock = new ReentrantLock(); + clone.aborted = false; + clone.releaseTrigger = null; + clone.connRequest = null; + clone.headergroup = (HeaderGroup) CloneUtils.clone(this.headergroup); + clone.params = (HttpParams) CloneUtils.clone(this.params); + return clone; + } + } diff --git a/module-client/src/main/java/org/apache/http/client/methods/HttpUriRequest.java b/module-client/src/main/java/org/apache/http/client/methods/HttpUriRequest.java index 87692944a..c18159e5a 100644 --- a/module-client/src/main/java/org/apache/http/client/methods/HttpUriRequest.java +++ b/module-client/src/main/java/org/apache/http/client/methods/HttpUriRequest.java @@ -61,4 +61,20 @@ public interface HttpUriRequest extends HttpRequest { */ URI getURI(); + /** + * Aborts execution of the request. + * + * @throws UnsupportedOperationException if the abort operation + * is not supported / cannot be implemented. + */ + void abort() throws UnsupportedOperationException; + + /** + * Tests if the request execution has been aborted. + * + * @return true if the request execution has been aborted, + * false otherwise. + */ + boolean isAborted(); + } diff --git a/module-client/src/main/java/org/apache/http/client/utils/CloneUtils.java b/module-client/src/main/java/org/apache/http/client/utils/CloneUtils.java new file mode 100644 index 000000000..fec534bc5 --- /dev/null +++ b/module-client/src/main/java/org/apache/http/client/utils/CloneUtils.java @@ -0,0 +1,75 @@ +/* + * $HeadURL:$ + * $Revision:$ + * $Date:$ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.http.client.utils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * A collection of utilities to workaround limitations of Java clone framework. + */ +public class CloneUtils { + + public static Object clone(final Object obj) throws CloneNotSupportedException { + if (obj == null) { + return null; + } + if (obj instanceof Cloneable) { + Class clazz = obj.getClass (); + Method m; + try { + m = clazz.getMethod("clone", (Class[]) null); + } catch (NoSuchMethodException ex) { + throw new NoSuchMethodError(ex.getMessage()); + } + try { + return m.invoke(obj, (Object []) null); + } catch (InvocationTargetException ex) { + Throwable cause = ex.getCause(); + if (cause instanceof CloneNotSupportedException) { + throw ((CloneNotSupportedException) cause); + } else { + throw new Error("Unexpected exception", cause); + } + } catch (IllegalAccessException ex) { + throw new IllegalAccessError(ex.getMessage()); + } + } else { + throw new CloneNotSupportedException(); + } + } + + /** + * This class should not be instantiated. + */ + private CloneUtils() { + } + +} diff --git a/module-client/src/main/java/org/apache/http/impl/client/RequestWrapper.java b/module-client/src/main/java/org/apache/http/impl/client/RequestWrapper.java index 260bc7184..669f56cee 100644 --- a/module-client/src/main/java/org/apache/http/impl/client/RequestWrapper.java +++ b/module-client/src/main/java/org/apache/http/impl/client/RequestWrapper.java @@ -137,6 +137,14 @@ class RequestWrapper extends AbstractHttpMessage implements HttpUriRequest { return new BasicRequestLine(method, uritext, ver); } + public void abort() throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + public boolean isAborted() { + return false; + } + public HttpRequest getOriginal() { return this.original; } diff --git a/module-client/src/main/java/org/apache/http/impl/cookie/BasicClientCookie.java b/module-client/src/main/java/org/apache/http/impl/cookie/BasicClientCookie.java index 376cc064e..072a8e417 100644 --- a/module-client/src/main/java/org/apache/http/impl/cookie/BasicClientCookie.java +++ b/module-client/src/main/java/org/apache/http/impl/cookie/BasicClientCookie.java @@ -57,7 +57,7 @@ import org.apache.http.cookie.SetCookie; * * @version $Revision$ */ -public class BasicClientCookie implements SetCookie, ClientCookie { +public class BasicClientCookie implements SetCookie, ClientCookie, Cloneable { /** * Default Constructor taking a name and a value. The value may be null. @@ -311,6 +311,13 @@ public class BasicClientCookie implements SetCookie, ClientCookie { public boolean containsAttribute(final String name) { return this.attribs.get(name) != null; } + + @Override + public Object clone() throws CloneNotSupportedException { + BasicClientCookie clone = (BasicClientCookie) super.clone(); + clone.attribs = new HashMap(this.attribs); + return clone; + } @Override public String toString() { @@ -342,7 +349,7 @@ public class BasicClientCookie implements SetCookie, ClientCookie { private final String name; /** Cookie attributes as specified by the origin server */ - private final Map attribs; + private Map attribs; /** Cookie value */ private String value; diff --git a/module-client/src/main/java/org/apache/http/impl/cookie/BasicClientCookie2.java b/module-client/src/main/java/org/apache/http/impl/cookie/BasicClientCookie2.java index 7608f50f4..87dfe051a 100644 --- a/module-client/src/main/java/org/apache/http/impl/cookie/BasicClientCookie2.java +++ b/module-client/src/main/java/org/apache/http/impl/cookie/BasicClientCookie2.java @@ -89,6 +89,13 @@ public class BasicClientCookie2 extends BasicClientCookie implements SetCookie2 public boolean isExpired(final Date date) { return this.discard || super.isExpired(date); } + + @Override + public Object clone() throws CloneNotSupportedException { + BasicClientCookie2 clone = (BasicClientCookie2) super.clone(); + clone.ports = this.ports.clone(); + return clone; + } } diff --git a/module-client/src/test/java/org/apache/http/client/methods/TestHttpRequestBase.java b/module-client/src/test/java/org/apache/http/client/methods/TestHttpRequestBase.java index 8c482c49f..7ab3106e9 100644 --- a/module-client/src/test/java/org/apache/http/client/methods/TestHttpRequestBase.java +++ b/module-client/src/test/java/org/apache/http/client/methods/TestHttpRequestBase.java @@ -1,7 +1,7 @@ /* - * $HeadURL:$ - * $Revision:$ - * $Date:$ + * $HeadURL$ + * $Revision$ + * $Date$ * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -30,10 +30,15 @@ package org.apache.http.client.methods; +import java.io.ByteArrayInputStream; import java.io.IOException; +import org.apache.http.Header; import org.apache.http.HttpVersion; +import org.apache.http.entity.InputStreamEntity; +import org.apache.http.entity.StringEntity; import org.apache.http.params.HttpProtocolParams; +import org.apache.http.util.LangUtils; import junit.framework.Test; import junit.framework.TestCase; @@ -72,4 +77,67 @@ public class TestHttpRequestBase extends TestCase { assertEquals("/", httpget.getRequestLine().getUri()); } + public void testCloneBasicRequests() throws Exception { + HttpGet httpget = new HttpGet("http://host/path"); + httpget.addHeader("h1", "this header"); + httpget.addHeader("h2", "that header"); + httpget.addHeader("h3", "all sorts of headers"); + httpget.getParams().setParameter("p1", Integer.valueOf(1)); + httpget.getParams().setParameter("p2", "whatever"); + HttpGet clone = (HttpGet) httpget.clone(); + + assertEquals(httpget.getMethod(), clone.getMethod()); + assertEquals(httpget.getURI(), clone.getURI()); + + Header[] headers1 = httpget.getAllHeaders(); + Header[] headers2 = clone.getAllHeaders(); + + assertTrue(LangUtils.equals(headers1, headers2)); + assertTrue(httpget.getParams() != clone.getParams()); + + assertEquals(Integer.valueOf(1), clone.getParams().getParameter("p1")); + assertEquals("whatever", clone.getParams().getParameter("p2")); + assertEquals(null, clone.getParams().getParameter("p3")); + } + + public void testCloneEntityEnclosingRequests() throws Exception { + HttpPost httppost = new HttpPost("http://host/path"); + httppost.addHeader("h1", "this header"); + httppost.addHeader("h2", "that header"); + httppost.addHeader("h3", "all sorts of headers"); + httppost.getParams().setParameter("p1", Integer.valueOf(1)); + httppost.getParams().setParameter("p2", "whatever"); + HttpPost clone = (HttpPost) httppost.clone(); + + assertEquals(httppost.getMethod(), clone.getMethod()); + assertEquals(httppost.getURI(), clone.getURI()); + + Header[] headers1 = httppost.getAllHeaders(); + Header[] headers2 = clone.getAllHeaders(); + + assertTrue(LangUtils.equals(headers1, headers2)); + assertTrue(httppost.getParams() != clone.getParams()); + + assertEquals(Integer.valueOf(1), clone.getParams().getParameter("p1")); + assertEquals("whatever", clone.getParams().getParameter("p2")); + assertEquals(null, clone.getParams().getParameter("p3")); + + assertNull(clone.getEntity()); + + StringEntity e1 = new StringEntity("stuff"); + httppost.setEntity(e1); + clone = (HttpPost) httppost.clone(); + assertTrue(clone.getEntity() instanceof StringEntity); + assertFalse(clone.getEntity().equals(e1)); + + ByteArrayInputStream instream = new ByteArrayInputStream(new byte[] {}); + InputStreamEntity e2 = new InputStreamEntity(instream, -1); + httppost.setEntity(e2); + + try { + httppost.clone(); + fail("CloneNotSupportedException should have been thrown"); + } catch (CloneNotSupportedException expected) { + } + } }