summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteffen Pingel2013-04-28 07:36:32 (EDT)
committer Gerrit Code Review @ Eclipse.org2013-05-24 21:44:04 (EDT)
commit21b8dbc313f5e89551129c2ef044ae8058076487 (patch)
treee1fdc65f30683d8ba5331548b84c2c398b114a32
parent330d23ffd01fd76b97f535d3e4b9ff439c04a96a (diff)
downloadorg.eclipse.mylyn.commons-21b8dbc313f5e89551129c2ef044ae8058076487.zip
org.eclipse.mylyn.commons-21b8dbc313f5e89551129c2ef044ae8058076487.tar.gz
org.eclipse.mylyn.commons-21b8dbc313f5e89551129c2ef044ae8058076487.tar.bz2
405650: ensure connections are closed when requests are interruptedrefs/changes/12/12312/19
Change-Id: I553da9c6d0e72576ed488bf8713b6fd1eb51ee63 Task-Url: https://bugs.eclipse.org/bugs/show_bug.cgi?id=405650
-rw-r--r--org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/net/NetUtil.java2
-rw-r--r--org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/operations/CancellableOperationMonitorThread.java162
-rw-r--r--org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/operations/ICancellableOperation.java26
-rw-r--r--org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/operations/MonitoredOperation.java10
-rw-r--r--org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/operations/OperationUtil.java3
-rw-r--r--org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/CancellableInputStream.java89
-rw-r--r--org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/CommonHttpClient.java13
-rw-r--r--org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/CommonHttpOperation.java6
-rw-r--r--org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/CommonHttpResponse.java98
-rw-r--r--org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/HttpUtil.java86
-rw-r--r--org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/internal/commons/repositories/http/core/PollingProtocolSocketFactory.java3
-rw-r--r--org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/internal/commons/repositories/http/core/PollingSslProtocolSocketFactory.java3
-rw-r--r--org.eclipse.mylyn.commons.repositories.http.tests/src/org/eclipse/mylyn/commons/repositories/http/tests/CommonHttpResponseTest.java90
-rw-r--r--org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/TestUrl.java24
-rw-r--r--org.eclipse.mylyn.commons.tests/src/org/eclipse/mylyn/commons/tests/AllCommonsTests.java5
-rw-r--r--org.eclipse.mylyn.commons.tests/src/org/eclipse/mylyn/commons/tests/operations/CancellableOperationMonitorThreadTest.java152
16 files changed, 705 insertions, 67 deletions
diff --git a/org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/net/NetUtil.java b/org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/net/NetUtil.java
index ad65ea8..7272a42 100644
--- a/org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/net/NetUtil.java
+++ b/org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/net/NetUtil.java
@@ -74,7 +74,9 @@ public class NetUtil {
* the current operation or null
* @throws IOException
* @see {@link Socket#connect(java.net.SocketAddress, int)}
+ * @deprecated
*/
+ @Deprecated
public static void connect(final Socket socket, InetSocketAddress address, int timeout,
MonitoredOperation<?> operation) throws IOException {
if (operation != null) {
diff --git a/org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/operations/CancellableOperationMonitorThread.java b/org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/operations/CancellableOperationMonitorThread.java
new file mode 100644
index 0000000..50d11ec
--- /dev/null
+++ b/org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/operations/CancellableOperationMonitorThread.java
@@ -0,0 +1,162 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Tasktop Technologies and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Tasktop Technologies - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.commons.core.operations;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Polls {@link ICancellableOperation} objects for cancellation and aborts the corresponding operations.
+ *
+ * @author Steffen Pingel
+ * @since 3.9
+ */
+public class CancellableOperationMonitorThread extends Thread {
+
+ private static final int DEFAULT_POLLING_INTERVAL = 1000;
+
+ private static CancellableOperationMonitorThread instance;
+
+ public static synchronized CancellableOperationMonitorThread getInstance() {
+ if (instance == null) {
+ instance = new CancellableOperationMonitorThread();
+ }
+ return instance;
+ }
+
+ private final List<ICancellableOperation> operations = new CopyOnWriteArrayList<ICancellableOperation>();
+
+ private final long pollingInterval;
+
+ private boolean shutdown;
+
+ public CancellableOperationMonitorThread() {
+ this(DEFAULT_POLLING_INTERVAL);
+ }
+
+ public CancellableOperationMonitorThread(long pollingInterval) {
+ this.pollingInterval = pollingInterval;
+ setDaemon(true);
+ }
+
+ /**
+ * Registers <code>operation</code> to be be monitored for cancellation. If the operation is complete it must be
+ * unregistered by invoking {@link #removeOperation(ICancellableOperation)}.
+ *
+ * @see #removeOperation(ICancellableOperation)
+ */
+ public synchronized void addOperation(ICancellableOperation operation) {
+ checkShutdown();
+ operations.add(operation);
+ if (!isAlive() && !shutdown) {
+ start();
+ } else {
+ notify();
+ }
+ }
+
+ /**
+ * Returns the polling interval in milliseconds.
+ */
+ public long getPollingInterval() {
+ return pollingInterval;
+ }
+
+ /**
+ * Checks all registered operations for cancellation. Checks all queued operations at most twice. Used for testing.
+ */
+ public synchronized void processOperations() throws InterruptedException {
+ if (operations.isEmpty()) {
+ throw new IllegalStateException("The list of operations is empty"); //$NON-NLS-1$
+ }
+ checkShutdown();
+ notify();
+ wait();
+ // ensure processing happens again in case the first notify happened while the queue was processing
+ notify();
+ wait();
+ }
+
+ /**
+ * Unregisters <code>operation</code> to be be monitored for cancellation.
+ *
+ * @see #removeOperation(ICancellableOperation)
+ */
+ public synchronized void removeOperation(ICancellableOperation operation) {
+ checkShutdown();
+ operations.remove(operation);
+ }
+
+ @Override
+ public void run() {
+ try {
+ while (true) {
+ for (ICancellableOperation opertion : operations) {
+ if (opertion.isCanceled()) {
+ opertion.abort();
+ }
+ }
+ synchronized (this) {
+ // notify threads waiting in processOnce()
+ notifyAll();
+
+ // check shutdown flag while holding this
+ if (shutdown) {
+ break;
+ }
+
+ if (operations.isEmpty()) {
+ wait();
+ } else {
+ wait(pollingInterval);
+ }
+ }
+ }
+ } catch (InterruptedException e) {
+ // shutdown
+ }
+ }
+
+ /**
+ * Stops the thread and waits for it to complete. Can be called multiple times.
+ *
+ * @throws InterruptedException
+ * thrown if an interrupted signal is received while waiting for shutdown to complete
+ */
+ public synchronized void shutdown() throws InterruptedException {
+ this.shutdown = true;
+ notify();
+ if (isAlive()) {
+ join();
+ }
+ }
+
+ /**
+ * Starts the thread.
+ *
+ * @throws IllegalStateException
+ * thrown if the thread was already shutdown
+ * @see #shutdown()
+ */
+ @Override
+ public synchronized void start() {
+ checkShutdown();
+ super.start();
+ }
+
+ private void checkShutdown() {
+ if (shutdown) {
+ throw new IllegalStateException("Already shutdown"); //$NON-NLS-1$
+ }
+ }
+
+}
diff --git a/org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/operations/ICancellableOperation.java b/org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/operations/ICancellableOperation.java
new file mode 100644
index 0000000..a8b9b74
--- /dev/null
+++ b/org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/operations/ICancellableOperation.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Tasktop Technologies and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Tasktop Technologies - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.commons.core.operations;
+
+/**
+ * An operation that can be cancelled.
+ *
+ * @since 3.9
+ */
+public interface ICancellableOperation extends ICancellable {
+
+ /**
+ * Returns <code>true</code> if this operation was requested to be cancelled.
+ */
+ public boolean isCanceled();
+
+}
diff --git a/org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/operations/MonitoredOperation.java b/org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/operations/MonitoredOperation.java
index 1d57401..1e3a6ac 100644
--- a/org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/operations/MonitoredOperation.java
+++ b/org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/operations/MonitoredOperation.java
@@ -28,7 +28,7 @@ import org.eclipse.osgi.util.NLS;
* @since 3.7
* @see OperationUtil#execute(IProgressMonitor, Operation)
*/
-public abstract class MonitoredOperation<T> extends Operation<T> implements ICancellable {
+public abstract class MonitoredOperation<T> extends Operation<T> implements ICancellableOperation {
private static ThreadLocal<MonitoredOperation<?>> currentOperation = new ThreadLocal<MonitoredOperation<?>>();
@@ -71,6 +71,14 @@ public abstract class MonitoredOperation<T> extends Operation<T> implements ICan
}
}
+ /**
+ * @since 3.9
+ */
+ @Override
+ public boolean isCanceled() {
+ return monitor.isCanceled();
+ }
+
public void addListener(ICancellable listener) {
listeners.add(listener);
}
diff --git a/org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/operations/OperationUtil.java b/org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/operations/OperationUtil.java
index a154b86..213d558 100644
--- a/org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/operations/OperationUtil.java
+++ b/org.eclipse.mylyn.commons.core/src/org/eclipse/mylyn/commons/core/operations/OperationUtil.java
@@ -75,6 +75,7 @@ public class OperationUtil {
/**
* @since 3.7
*/
+ @Deprecated
public static <T> T execute(IProgressMonitor monitor, Operation<T> request) throws Throwable {
// check for legacy reasons
SubMonitor subMonitor = (monitor instanceof SubMonitor) ? (SubMonitor) monitor : SubMonitor.convert(null);
@@ -102,6 +103,8 @@ public class OperationUtil {
try {
return future.get(POLL_INTERVAL, TimeUnit.MILLISECONDS);
+ } catch (CancellationException e) {
+ throw new OperationCanceledException();
} catch (ExecutionException e) {
// XXX this hides the original stack trace from the caller invoking execute()
throw e.getCause();
diff --git a/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/CancellableInputStream.java b/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/CancellableInputStream.java
new file mode 100644
index 0000000..577e976
--- /dev/null
+++ b/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/CancellableInputStream.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Tasktop Technologies and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Tasktop Technologies - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.commons.repositories.http.core;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.OperationCanceledException;
+
+/**
+ * @author Steffen Pingel
+ */
+class CancellableInputStream extends FilterInputStream {
+
+ private volatile boolean cancelled;
+
+ private final CommonHttpResponse response;
+
+ public CancellableInputStream(CommonHttpResponse response, InputStream in) {
+ super(in);
+ Assert.isNotNull(response);
+ this.response = response;
+ }
+
+ @Override
+ public int available() throws IOException {
+ checkCancelled();
+ try {
+ return super.available();
+ } catch (IOException e) {
+ checkCancelled();
+ throw e;
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ response.notifyStreamClosed();
+ super.close();
+ }
+
+ void closeWithoutNotification() throws IOException {
+ super.close();
+ }
+
+ @Override
+ public int read() throws IOException {
+ checkCancelled();
+ try {
+ return super.read();
+ } catch (IOException e) {
+ checkCancelled();
+ throw e;
+ }
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ checkCancelled();
+ try {
+ return super.read(b, off, len);
+ } catch (IOException e) {
+ checkCancelled();
+ throw e;
+ }
+ }
+
+ private void checkCancelled() {
+ if (cancelled) {
+ throw new OperationCanceledException();
+ }
+ }
+
+ void cancel() {
+ cancelled = true;
+ }
+
+}
diff --git a/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/CommonHttpClient.java b/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/CommonHttpClient.java
index 7b8093b..536361e 100644
--- a/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/CommonHttpClient.java
+++ b/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/CommonHttpClient.java
@@ -31,6 +31,7 @@ import org.apache.http.protocol.SyncBasicHttpContext;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.mylyn.commons.core.net.SslSupport;
import org.eclipse.mylyn.commons.core.net.TrustAllTrustManager;
+import org.eclipse.mylyn.commons.core.operations.CancellableOperationMonitorThread;
import org.eclipse.mylyn.commons.core.operations.IOperationMonitor;
import org.eclipse.mylyn.commons.repositories.core.RepositoryLocation;
import org.eclipse.mylyn.commons.repositories.core.auth.AuthenticationCredentials;
@@ -59,6 +60,8 @@ public class CommonHttpClient {
private final RepositoryLocation location;
+ private CancellableOperationMonitorThread monitorThread = CancellableOperationMonitorThread.getInstance();
+
public CommonHttpClient(RepositoryLocation location) {
this.location = location;
this.context = new SyncBasicHttpContext(null);
@@ -152,6 +155,8 @@ public class CommonHttpClient {
// remove the token that associates certificate credentials with the connection
context.removeAttribute(ClientContext.USER_TOKEN);
}
+
+ context.setAttribute(HttpUtil.CONTEXT_KEY_MONITOR_THREAD, getMonitorThread());
}
protected void authenticate(IOperationMonitor monitor) throws IOException {
@@ -197,4 +202,12 @@ public class CommonHttpClient {
}
}
+ public CancellableOperationMonitorThread getMonitorThread() {
+ return monitorThread;
+ }
+
+ public void setMonitorThread(CancellableOperationMonitorThread monitorThread) {
+ this.monitorThread = monitorThread;
+ }
+
}
diff --git a/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/CommonHttpOperation.java b/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/CommonHttpOperation.java
index 831e5d3..a569974 100644
--- a/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/CommonHttpOperation.java
+++ b/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/CommonHttpOperation.java
@@ -118,7 +118,7 @@ public abstract class CommonHttpOperation<T> {
try {
validate(response, monitor);
// success
- return new CommonHttpResponse(request, response);
+ return new CommonHttpResponse(request, response, client.getMonitorThread(), monitor);
} catch (IOException e) {
HttpUtil.release(request, response, monitor);
throw e;
@@ -136,8 +136,8 @@ public abstract class CommonHttpOperation<T> {
return client.needsAuthentication();
}
- protected <T extends AuthenticationCredentials> T requestCredentials(
- AuthenticationRequest<AuthenticationType<T>> request, IOperationMonitor monitor) {
+ protected <C extends AuthenticationCredentials> C requestCredentials(
+ AuthenticationRequest<AuthenticationType<C>> request, IOperationMonitor monitor) {
return client.requestCredentials(request, monitor);
}
diff --git a/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/CommonHttpResponse.java b/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/CommonHttpResponse.java
index 5b237a7..645596f 100644
--- a/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/CommonHttpResponse.java
+++ b/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/CommonHttpResponse.java
@@ -21,57 +21,127 @@ import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.util.EntityUtils;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.mylyn.commons.core.operations.CancellableOperationMonitorThread;
+import org.eclipse.mylyn.commons.core.operations.ICancellableOperation;
+import org.eclipse.mylyn.commons.core.operations.IOperationMonitor;
/**
* @author Steffen Pingel
*/
-public class CommonHttpResponse {
+public class CommonHttpResponse implements ICancellableOperation {
+
+ private CancellableInputStream entityStream;
+
+ private final IOperationMonitor monitor;
private final HttpRequest request;
private final HttpResponse response;
- public CommonHttpResponse(HttpRequest request, HttpResponse response) {
+ private final CancellableOperationMonitorThread monitorThread;
+
+ public CommonHttpResponse(HttpRequest request, HttpResponse response,
+ CancellableOperationMonitorThread monitorThread, IOperationMonitor monitor) {
Assert.isNotNull(request);
Assert.isNotNull(response);
+ Assert.isNotNull(monitorThread);
+ Assert.isNotNull(monitor);
this.request = request;
this.response = response;
+ this.monitorThread = monitorThread;
+ this.monitor = monitor;
+ }
+
+ @Override
+ public void abort() {
+ abortStream();
+ if (request instanceof HttpUriRequest) {
+ try {
+ ((HttpUriRequest) request).abort();
+ } catch (UnsupportedOperationException e) {
+ }
+ }
}
public HttpRequest getRequest() {
return request;
}
+ public String getRequestPath() {
+ if (request instanceof HttpUriRequest) {
+ return ((HttpUriRequest) request).getURI().getPath();
+ } else {
+ return null;
+ }
+ }
+
public HttpResponse getResponse() {
return response;
}
- public int getStatusCode() {
- return response.getStatusLine().getStatusCode();
+ public String getResponseCharSet() {
+ return EntityUtils.getContentCharSet(response.getEntity());
}
- public InputStream getResponseEntityAsStream(IProgressMonitor monitor) throws IOException {
+ public synchronized InputStream getResponseEntityAsStream() throws IOException {
+ if (entityStream != null) {
+ throw new IllegalStateException();
+ }
HttpEntity entity = response.getEntity();
if (entity == null) {
throw new IOException("Expected entity"); //$NON-NLS-1$
}
- return HttpUtil.getResponseBodyAsStream(entity, monitor);
+ entityStream = new CancellableInputStream(this, entity.getContent());
+ monitorThread.addOperation(this);
+ return entityStream;
}
- public void release(IProgressMonitor monitor) {
+ /**
+ * @deprecated use {@link #getResponseEntityAsStream()} instead
+ */
+ @Deprecated
+ public InputStream getResponseEntityAsStream(IOperationMonitor monitor) throws IOException {
+ return getResponseEntityAsStream();
+ }
+
+ public int getStatusCode() {
+ return response.getStatusLine().getStatusCode();
+ }
+
+ @Override
+ public boolean isCanceled() {
+ return monitor.isCanceled();
+ }
+
+ public void release() {
+ releaseStream();
HttpUtil.release(request, response, monitor);
}
- public String getRequestPath() {
- if (request instanceof HttpUriRequest) {
- return ((HttpUriRequest) request).getURI().getPath();
- } else {
- return null;
+ /**
+ * @deprecated use {@link #release()} instead
+ */
+ @Deprecated
+ public void release(IProgressMonitor monitor) {
+ release();
+ }
+
+ private synchronized void abortStream() {
+ if (entityStream != null) {
+ CancellableOperationMonitorThread.getInstance().removeOperation(this);
+ entityStream.cancel();
}
}
- public String getResponseCharSet() {
- return EntityUtils.getContentCharSet(response.getEntity());
+ private synchronized void releaseStream() {
+ if (entityStream != null) {
+ CancellableOperationMonitorThread.getInstance().removeOperation(this);
+ entityStream = null;
+ }
+ }
+
+ void notifyStreamClosed() {
+ release();
}
}
diff --git a/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/HttpUtil.java b/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/HttpUtil.java
index 0b87004..41f7745 100644
--- a/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/HttpUtil.java
+++ b/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/commons/repositories/http/core/HttpUtil.java
@@ -13,6 +13,7 @@ package org.eclipse.mylyn.commons.repositories.http.core;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
@@ -49,13 +50,15 @@ import org.eclipse.core.net.proxy.IProxyData;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.mylyn.commons.core.CoreUtil;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.commons.core.net.AuthenticatedProxy;
import org.eclipse.mylyn.commons.core.net.NetUtil;
-import org.eclipse.mylyn.commons.core.operations.MonitoredOperation;
-import org.eclipse.mylyn.commons.core.operations.Operation;
+import org.eclipse.mylyn.commons.core.operations.CancellableOperationMonitorThread;
+import org.eclipse.mylyn.commons.core.operations.ICancellableOperation;
+import org.eclipse.mylyn.commons.core.operations.IOperationMonitor;
import org.eclipse.mylyn.commons.core.operations.OperationUtil;
import org.eclipse.mylyn.commons.repositories.core.RepositoryLocation;
import org.eclipse.mylyn.commons.repositories.core.auth.AuthenticationType;
@@ -108,6 +111,8 @@ public class HttpUtil {
private static ThreadSafeClientConnManager connectionManager;
+ static final String CONTEXT_KEY_MONITOR_THREAD = CancellableOperationMonitorThread.class.getName();
+
public static void configureClient(AbstractHttpClient client, String userAgent) {
HttpClientParams.setCookiePolicy(client.getParams(), CookiePolicy.BEST_MATCH);
@@ -161,26 +166,42 @@ public class HttpUtil {
}
public static HttpResponse execute(final AbstractHttpClient client, final HttpHost host, final HttpContext context,
- final HttpRequestBase method, IProgressMonitor monitor) throws IOException {
+ final HttpRequestBase method, final IProgressMonitor progress) throws IOException {
Assert.isNotNull(client);
Assert.isNotNull(method);
- monitor = OperationUtil.convert(monitor);
-
- MonitoredOperation<HttpResponse> executor = new MonitoredOperation<HttpResponse>(monitor) {
+ final IOperationMonitor monitor = OperationUtil.convert(progress);
+ ICancellableOperation operation = new ICancellableOperation() {
@Override
public void abort() {
- super.abort();
method.abort();
}
@Override
- public HttpResponse execute() throws Exception {
- return client.execute(host, method, context);
+ public boolean isCanceled() {
+ return monitor.isCanceled();
}
};
- return executeInternal(monitor, executor);
+ CancellableOperationMonitorThread thread = null;
+ if (context != null) {
+ thread = (CancellableOperationMonitorThread) context.getAttribute(CONTEXT_KEY_MONITOR_THREAD);
+ }
+ if (thread != null) {
+ thread.addOperation(operation);
+ }
+ try {
+ return client.execute(host, method, context);
+ } catch (InterruptedIOException e) {
+ if (monitor.isCanceled()) {
+ throw new OperationCanceledException();
+ }
+ throw e;
+ } finally {
+ if (thread != null) {
+ thread.removeOperation(operation);
+ }
+ }
}
public static NTCredentials getNtCredentials(UserCredentials credentials, String workstation) {
@@ -266,21 +287,6 @@ public class HttpUtil {
}
}
- @SuppressWarnings("unchecked")
- private static <T> T executeInternal(IProgressMonitor monitor, Operation<?> request) throws IOException {
- try {
- return (T) OperationUtil.execute(monitor, request);
- } catch (IOException e) {
- throw e;
- } catch (RuntimeException e) {
- throw e;
- } catch (Error e) {
- throw e;
- } catch (Throwable e) {
- throw new RuntimeException(e);
- }
- }
-
static Credentials getCredentials(final String username, final String password, final InetAddress address,
boolean forceUserNamePassword) {
int i = username.indexOf("\\"); //$NON-NLS-1$
@@ -335,26 +341,24 @@ public class HttpUtil {
((HttpUriRequest) request).abort();
} catch (UnsupportedOperationException e) {
// fall back to standard close
- try {
- EntityUtils.consume(response.getEntity());
- } catch (IOException e2) {
- // ignore
- } catch (NullPointerException e2) {
- // XXX work-around for bug 368830
- }
+ consume(request, response);
}
} else {
+ consume(request, response);
+ }
+ }
+
+ private static void consume(HttpRequest request, HttpResponse response) {
+ try {
+ EntityUtils.consume(response.getEntity());
+ } catch (IOException e) {
+ // if construction of the stream fails the connection has to be aborted to be released
try {
- EntityUtils.consume(response.getEntity());
- } catch (IOException e) {
- // if construction of the stream fails the connection has to be aborted to be released
- try {
- ((HttpUriRequest) request).abort();
- } catch (UnsupportedOperationException e2) {
- }
- } catch (NullPointerException e) {
- // XXX work-around for bug 368830
+ ((HttpUriRequest) request).abort();
+ } catch (UnsupportedOperationException e2) {
}
+ } catch (NullPointerException e2) {
+ // XXX work-around for bug 368830
}
}
diff --git a/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/internal/commons/repositories/http/core/PollingProtocolSocketFactory.java b/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/internal/commons/repositories/http/core/PollingProtocolSocketFactory.java
index 804d21e..7c58ba6 100644
--- a/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/internal/commons/repositories/http/core/PollingProtocolSocketFactory.java
+++ b/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/internal/commons/repositories/http/core/PollingProtocolSocketFactory.java
@@ -23,7 +23,6 @@ import org.apache.http.conn.scheme.SchemeSocketFactory;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.eclipse.mylyn.commons.core.net.NetUtil;
-import org.eclipse.mylyn.commons.core.operations.MonitoredOperation;
/**
* @author Steffen Pingel
@@ -48,7 +47,7 @@ public class PollingProtocolSocketFactory implements SchemeSocketFactory {
int connTimeout = HttpConnectionParams.getConnectionTimeout(params);
socket.bind(localAddress);
- NetUtil.connect(socket, remoteAddress, connTimeout, MonitoredOperation.getCurrentOperation());
+ socket.connect(remoteAddress, connTimeout);
return socket;
}
diff --git a/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/internal/commons/repositories/http/core/PollingSslProtocolSocketFactory.java b/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/internal/commons/repositories/http/core/PollingSslProtocolSocketFactory.java
index f17d816..29dd3b9 100644
--- a/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/internal/commons/repositories/http/core/PollingSslProtocolSocketFactory.java
+++ b/org.eclipse.mylyn.commons.repositories.http.core/src/org/eclipse/mylyn/internal/commons/repositories/http/core/PollingSslProtocolSocketFactory.java
@@ -28,7 +28,6 @@ import org.eclipse.core.runtime.Assert;
import org.eclipse.mylyn.commons.core.net.NetUtil;
import org.eclipse.mylyn.commons.core.net.SslSupport;
import org.eclipse.mylyn.commons.core.net.TrustAllTrustManager;
-import org.eclipse.mylyn.commons.core.operations.MonitoredOperation;
/**
* Provides support for managing SSL connections.
@@ -58,7 +57,7 @@ public class PollingSslProtocolSocketFactory implements LayeredSchemeSocketFacto
}
int connTimeout = HttpConnectionParams.getConnectionTimeout(params);
- NetUtil.connect(socket, remoteAddress, connTimeout, MonitoredOperation.getCurrentOperation());
+ socket.connect(remoteAddress, connTimeout);
if (socket instanceof SSLSocket) {
return socket;
diff --git a/org.eclipse.mylyn.commons.repositories.http.tests/src/org/eclipse/mylyn/commons/repositories/http/tests/CommonHttpResponseTest.java b/org.eclipse.mylyn.commons.repositories.http.tests/src/org/eclipse/mylyn/commons/repositories/http/tests/CommonHttpResponseTest.java
new file mode 100644
index 0000000..6ee67d8
--- /dev/null
+++ b/org.eclipse.mylyn.commons.repositories.http.tests/src/org/eclipse/mylyn/commons/repositories/http/tests/CommonHttpResponseTest.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Tasktop Technologies and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Tasktop Technologies - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.commons.repositories.http.tests;
+
+import static org.junit.Assert.fail;
+
+import java.io.InputStream;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.mylyn.commons.core.operations.CancellableOperationMonitorThread;
+import org.eclipse.mylyn.commons.repositories.core.RepositoryLocation;
+import org.eclipse.mylyn.commons.repositories.http.core.CommonHttpClient;
+import org.eclipse.mylyn.commons.repositories.http.core.CommonHttpResponse;
+import org.eclipse.mylyn.commons.sdk.util.TestUrl;
+import org.eclipse.mylyn.internal.commons.core.operations.NullOperationMonitor;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Steffen Pingel
+ */
+public class CommonHttpResponseTest {
+
+ private final TestUrl urls = TestUrl.DEFAULT;
+
+ private NullOperationMonitor monitor;
+
+ private CommonHttpResponse response;
+
+ private final CancellableOperationMonitorThread monitorThread = new CancellableOperationMonitorThread();
+
+ @Before
+ public void setUp() throws Exception {
+ monitor = new NullOperationMonitor();
+ RepositoryLocation location = new RepositoryLocation();
+ location.setUrl(urls.getHttpOk().toString());
+
+ HttpGet request = new HttpGet(location.getUrl());
+ CommonHttpClient client = new CommonHttpClient(location);
+ HttpResponse clientResponse = client.execute(request, monitor);
+ response = new CommonHttpResponse(request, clientResponse, monitorThread, monitor);
+ }
+
+ @After
+ public void tearDown() {
+ if (response != null) {
+ response.release();
+ }
+ }
+
+ @Test
+ public void testCancel() throws Exception {
+ monitor.setCanceled(true);
+ InputStream in = response.getResponseEntityAsStream();
+ monitorThread.processOperations();
+ try {
+ in.read();
+ fail("Expected OperationCancelledException");
+ } catch (OperationCanceledException e) {
+ // ignore
+ }
+ }
+
+ @Test
+ public void testCancelAfterRead() throws Exception {
+ InputStream in = response.getResponseEntityAsStream();
+ in.read();
+ monitor.setCanceled(true);
+ monitorThread.processOperations();
+ try {
+ in.read();
+ fail("Expected OperationCancelledException");
+ } catch (OperationCanceledException e) {
+ // ignore
+ }
+ }
+
+}
diff --git a/org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/TestUrl.java b/org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/TestUrl.java
index 24424b6..3e2e038 100644
--- a/org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/TestUrl.java
+++ b/org.eclipse.mylyn.commons.sdk.util/src/org/eclipse/mylyn/commons/sdk/util/TestUrl.java
@@ -11,7 +11,10 @@
package org.eclipse.mylyn.commons.sdk.util;
+import java.io.IOException;
+import java.net.InetSocketAddress;
import java.net.MalformedURLException;
+import java.net.Socket;
import java.net.URL;
/**
@@ -21,7 +24,7 @@ import java.net.URL;
*/
public class TestUrl {
- public static final TestUrl DEFAULT = new TestUrl();
+ public static final TestUrl DEFAULT = probeLocalhost();
private final String URL_HTTP_404_NOT_FOUND = "http://mylyn.org/notfound";
@@ -35,10 +38,22 @@ public class TestUrl {
private final String URL_HTTPS_OK = "https://mylyn.org/";
+ private final String host;
+
public URL getConnectionRefused() {
return createUrl(URL_HTTP_CONNECTION_REFUSED);
}
+ private static TestUrl probeLocalhost() {
+ Socket socket = new Socket();
+ try {
+ socket.connect(new InetSocketAddress("localhost", 2080), 100);
+ return new TestUrl("localhost");
+ } catch (IOException e) {
+ return new TestUrl(null);
+ }
+ }
+
public URL getConnectionTimeout() {
return createUrl(URL_HTTP_CONNECTION_TIMEOUT);
}
@@ -60,6 +75,9 @@ public class TestUrl {
}
private URL createUrl(String url) {
+ if (host != null) {
+ url = url.replace("mylyn.org", host);
+ }
try {
return new URL(url);
} catch (MalformedURLException e) {
@@ -67,8 +85,8 @@ public class TestUrl {
}
}
- private TestUrl() {
- // not intended to be instantiated
+ private TestUrl(String host) {
+ this.host = host;
}
}
diff --git a/org.eclipse.mylyn.commons.tests/src/org/eclipse/mylyn/commons/tests/AllCommonsTests.java b/org.eclipse.mylyn.commons.tests/src/org/eclipse/mylyn/commons/tests/AllCommonsTests.java
index a215da5..7429fb9 100644
--- a/org.eclipse.mylyn.commons.tests/src/org/eclipse/mylyn/commons/tests/AllCommonsTests.java
+++ b/org.eclipse.mylyn.commons.tests/src/org/eclipse/mylyn/commons/tests/AllCommonsTests.java
@@ -15,6 +15,7 @@ import junit.framework.Test;
import junit.framework.TestSuite;
import org.eclipse.mylyn.commons.core.HtmlStreamTokenizerTest;
+import org.eclipse.mylyn.commons.sdk.util.ManagedTestSuite;
import org.eclipse.mylyn.commons.tests.core.AuthenticatedProxyTest;
import org.eclipse.mylyn.commons.tests.core.CommonListenerListTest;
import org.eclipse.mylyn.commons.tests.core.CoreUtilTest;
@@ -26,6 +27,7 @@ import org.eclipse.mylyn.commons.tests.net.NetUtilTest;
import org.eclipse.mylyn.commons.tests.net.SslProtocolSocketFactoryTest;
import org.eclipse.mylyn.commons.tests.net.TimeoutInputStreamTest;
import org.eclipse.mylyn.commons.tests.net.WebUtilTest;
+import org.eclipse.mylyn.commons.tests.operations.CancellableOperationMonitorThreadTest;
import org.eclipse.mylyn.commons.tests.operations.OperationUtilTest;
import org.eclipse.mylyn.commons.tests.workbench.browser.BrowserUtilTest;
@@ -35,7 +37,7 @@ import org.eclipse.mylyn.commons.tests.workbench.browser.BrowserUtilTest;
public class AllCommonsTests {
public static Test suite() {
- TestSuite suite = new TestSuite(AllCommonsTests.class.getName());
+ TestSuite suite = new ManagedTestSuite(AllCommonsTests.class.getName());
suite.addTestSuite(TimeoutInputStreamTest.class);
suite.addTestSuite(CoreUtilTest.class);
suite.addTestSuite(AuthenticatedProxyTest.class);
@@ -50,6 +52,7 @@ public class AllCommonsTests {
suite.addTestSuite(Html2TextReaderTest.class);
suite.addTestSuite(CommonHttpMethod3Test.class);
suite.addTestSuite(HtmlStreamTokenizerTest.class);
+ suite.addTestSuite(CancellableOperationMonitorThreadTest.class);
return suite;
}
diff --git a/org.eclipse.mylyn.commons.tests/src/org/eclipse/mylyn/commons/tests/operations/CancellableOperationMonitorThreadTest.java b/org.eclipse.mylyn.commons.tests/src/org/eclipse/mylyn/commons/tests/operations/CancellableOperationMonitorThreadTest.java
new file mode 100644
index 0000000..fef03fd
--- /dev/null
+++ b/org.eclipse.mylyn.commons.tests/src/org/eclipse/mylyn/commons/tests/operations/CancellableOperationMonitorThreadTest.java
@@ -0,0 +1,152 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Tasktop Technologies and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Tasktop Technologies - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.commons.tests.operations;
+
+import junit.framework.TestCase;
+
+import org.eclipse.mylyn.commons.core.operations.CancellableOperationMonitorThread;
+import org.eclipse.mylyn.commons.core.operations.ICancellableOperation;
+import org.junit.Test;
+
+/**
+ * @author Steffen Pingel
+ */
+public class CancellableOperationMonitorThreadTest extends TestCase {
+
+ private CancellableOperationMonitorThread thread;
+
+ class MockOperation implements ICancellableOperation {
+
+ boolean canceled;
+
+ boolean aborted;
+
+ @Override
+ public void abort() {
+ aborted = true;
+ }
+
+ @Override
+ public boolean isCanceled() {
+ return canceled;
+ }
+
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ thread = new CancellableOperationMonitorThread();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ thread.shutdown();
+ }
+
+ @Test
+ public void testShutdownAddOperation() throws InterruptedException {
+ thread.shutdown();
+ try {
+ thread.addOperation(new MockOperation());
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ @Test
+ public void testShutdownProcessOnce() throws InterruptedException {
+ thread.shutdown();
+ try {
+ thread.processOperations();
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ @Test
+ public void testShutdownRemoveOperation() throws InterruptedException {
+ thread.shutdown();
+ try {
+ thread.removeOperation(new MockOperation());
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ @Test
+ public void testShutdownStart() throws InterruptedException {
+ thread.shutdown();
+ try {
+ thread.start();
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ @Test
+ public void testShutdownTwice() throws InterruptedException {
+ thread.start();
+ assertTrue(thread.isAlive());
+ thread.shutdown();
+ assertFalse(thread.isAlive());
+ thread.shutdown();
+ assertFalse(thread.isAlive());
+ }
+
+ @Test
+ public void testShutdown() throws Exception {
+ assertFalse(thread.isAlive());
+ thread.start();
+ assertTrue(thread.isAlive());
+ thread.shutdown();
+ assertFalse(thread.isAlive());
+ }
+
+ @Test
+ public void testShutdownNotStarted() throws Exception {
+ assertFalse(thread.isAlive());
+ thread.shutdown();
+ assertFalse(thread.isAlive());
+ }
+
+ public void testNotCancelOperation() throws Exception {
+ MockOperation operation = new MockOperation();
+ thread.addOperation(operation);
+ assertFalse(operation.aborted);
+ thread.processOperations();
+ assertFalse(operation.aborted);
+ }
+
+ public void testCancelOperation() throws Exception {
+ MockOperation operation = new MockOperation();
+ thread.addOperation(operation);
+ assertFalse(operation.aborted);
+ operation.canceled = true;
+ thread.processOperations();
+ assertTrue(operation.aborted);
+ }
+
+ public void testAddRemoveOperation() throws Exception {
+ MockOperation operation = new MockOperation();
+ thread.addOperation(operation);
+ assertTrue(thread.isAlive());
+ thread.removeOperation(operation);
+ assertTrue(thread.isAlive());
+ operation.canceled = true;
+ try {
+ thread.processOperations();
+ } catch (IllegalStateException expected) {
+ }
+ assertFalse(operation.aborted);
+ }
+
+}