diff options
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 ad65ea8c..7272a422 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 00000000..50d11ec0 --- /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 00000000..a8b9b749 --- /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 1d57401a..1e3a6acb 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 a154b860..213d5586 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 00000000..577e9769 --- /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 7b8093b3..536361e5 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 831e5d3b..a5699741 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 5b237a76..645596f5 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 0b870047..41f7745b 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 804d21e1..7c58ba6f 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 f17d8160..29dd3b9a 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 00000000..6ee67d82 --- /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 24424b67..3e2e038d 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 a215da52..7429fb9a 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 00000000..fef03fd4 --- /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); + } + +} |