/******************************************************************************* * Copyright (c) 2009 IBM, 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: * IBM Corporation - initial API and implementation ******************************************************************************/ package org.eclipse.ecf.tests.filetransfer; import java.io.File; import java.io.IOException; import java.net.URL; import org.apache.commons.httpclient.server.HttpRequestHandler; import org.apache.commons.httpclient.server.ResponseWriter; import org.apache.commons.httpclient.server.SimpleHttpServer; import org.apache.commons.httpclient.server.SimpleHttpServerConnection; import org.apache.commons.httpclient.server.SimpleRequest; import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.ecf.filetransfer.FileTransferJob; import org.eclipse.ecf.filetransfer.IFileTransferListener; import org.eclipse.ecf.filetransfer.UserCancelledException; import org.eclipse.ecf.filetransfer.events.IFileTransferConnectStartEvent; import org.eclipse.ecf.filetransfer.events.IIncomingFileTransferReceiveDataEvent; import org.eclipse.ecf.filetransfer.events.IIncomingFileTransferReceiveDoneEvent; import org.eclipse.ecf.filetransfer.events.IIncomingFileTransferReceiveStartEvent; import org.eclipse.ecf.filetransfer.events.socket.ISocketConnectedEvent; import org.eclipse.ecf.filetransfer.events.socket.ISocketEvent; import org.eclipse.ecf.filetransfer.events.socket.ISocketEventSource; import org.eclipse.ecf.filetransfer.events.socket.ISocketListener; import org.eclipse.ecf.filetransfer.identity.IFileID; import org.eclipse.ecf.internal.tests.filetransfer.httpserver.SimpleServer; import org.eclipse.ecf.provider.filetransfer.retrieve.AbstractRetrieveFileTransfer; import org.eclipse.ecf.tests.filetransfer.SocketEventTestUtil.SocketInReadWrapper; import org.eclipse.ecf.tests.filetransfer.SocketEventTestUtil.TrackSocketEvents; public class URLRetrieveTestCancelConnectJob extends AbstractRetrieveTestCase { File tmpFile = null; private TrackSocketEvents socketEvents; private SocketInReadWrapper socketInReadWrapper; private boolean CANCEL_SUPPORTED_ON_CONNECT = new Boolean( System.getProperty( "org.eclipse.ecf.tests.filetransfer.cancelSupportedOnConnect", "true")).booleanValue(); /* * (non-Javadoc) * * @see junit.framework.TestCase#setUp() */ protected void setUp() throws Exception { super.setUp(); } protected void createTempFile() throws IOException { tmpFile = File.createTempFile("ECFTest", ""); } /* * (non-Javadoc) * * @see junit.framework.TestCase#tearDown() */ protected void tearDown() throws Exception { super.tearDown(); if (tmpFile != null) tmpFile.delete(); tmpFile = null; } /* * (non-Javadoc) * * @see org.eclipse.ecf.tests.filetransfer.AbstractRetrieveTestCase# * handleStartConnectEvent * (org.eclipse.ecf.filetransfer.events.IFileTransferConnectStartEvent) */ protected void handleStartConnectEvent(IFileTransferConnectStartEvent event) { super.handleStartConnectEvent(event); this.socketEvents = SocketEventTestUtil.observeSocketEvents(event); ISocketEventSource source = (ISocketEventSource) event .getAdapter(ISocketEventSource.class); source.addListener(new ISocketListener() { public void handleSocketEvent(ISocketEvent event) { if (event instanceof ISocketConnectedEvent) { ISocketConnectedEvent connectedEvent = (ISocketConnectedEvent) event; socketInReadWrapper = new SocketInReadWrapper( connectedEvent.getSocket(), startTime); connectedEvent.setSocket(socketInReadWrapper); } } }); } protected void handleDoneEvent(IIncomingFileTransferReceiveDoneEvent event) { super.handleDoneEvent(event); assertTrue(event.getSource().getException() instanceof UserCancelledException); } protected void testReceive(String url, IFileTransferListener listener) throws Exception { assertNotNull(retrieveAdapter); final IFileID fileID = createFileID(new URL(url)); retrieveAdapter.sendRetrieveRequest(fileID, listener, null); waitForDone(10000); } public void testReceiveFile_cancelOnConnectEvent() throws Exception { if (!CANCEL_SUPPORTED_ON_CONNECT) { trace("WARNING: Cancel not supported by this provider. testReceiveFile_cancelOnConnectEvent cannot be used"); return; } final IFileTransferListener listener = createFileTransferListener(); final FileTransferListenerWrapper lw = new FileTransferListenerWrapper( listener) { protected void handleStartConnectEvent( IFileTransferConnectStartEvent event) { assertNotNull(event.getFileID()); assertNotNull(event.getFileID().getFilename()); assertNull(socketInReadWrapper); setDone(true); event.cancel(); } }; testReceive(URLRetrieveTest.HTTP_RETRIEVE, lw); assertHasEvent(startConnectEvents, IFileTransferConnectStartEvent.class); assertHasNoEvent(startEvents, IIncomingFileTransferReceiveStartEvent.class); assertHasNoEvent(dataEvents, IIncomingFileTransferReceiveDataEvent.class); assertDoneCancelled(); assertNull(tmpFile); socketEvents.validateNoSocketCreated(); } // TODO: add test that cancel without connect job, when server does not // respond public void testReceiveFile_cancelConnectJob() throws Exception { if (!CANCEL_SUPPORTED_ON_CONNECT) { trace("WARNING: Cancel not supported by this provider. testReceiveFile_cancelConnectJob cannot be used"); return; } final Object[] doCancel = new Object[1]; final IFileTransferListener listener = createFileTransferListener(); final FileTransferListenerWrapper lw = new FileTransferListenerWrapper( listener) { protected void handleStartConnectEvent( final IFileTransferConnectStartEvent event) { assertNotNull(event.getFileID()); assertNotNull(event.getFileID().getFilename()); FileTransferJob connectJob = event.prepareConnectJob(null); connectJob.addJobChangeListener(new JobChangeTraceListener( startTime) { public void running(IJobChangeEvent jobEvent) { super.running(jobEvent); spawnCancelThread(doCancel, new ICancelable() { public void cancel() { assertNotNull(socketInReadWrapper); assertTrue(socketInReadWrapper.inRead); event.cancel(); } }); } }); event.connectUsingJob(connectJob); } }; final SimpleServer server = new SimpleServer(getName()); SimpleHttpServer simple = server.getSimpleHttpServer(); simple.setRequestHandler(new HttpRequestHandler() { public boolean processRequest(SimpleHttpServerConnection conn, SimpleRequest request) throws IOException { trace("Not responding to request " + request.getRequestLine()); return stalledInRequestHandler(doCancel); } }); try { // path does not matter as server does not respond. testReceive(server.getServerURL() + "/foo", lw); assertHasEvent(startConnectEvents, IFileTransferConnectStartEvent.class); assertHasNoEvent(startEvents, IIncomingFileTransferReceiveStartEvent.class); assertHasNoEvent(dataEvents, IIncomingFileTransferReceiveDataEvent.class); IIncomingFileTransferReceiveDoneEvent doneEvent = getDoneEvent(); assertTrue(doneEvent.getException().toString(), doneEvent.getException() instanceof UserCancelledException); assertTrue(doneEvent.getSource().isDone()); assertSame(doneEvent.getException(), doneEvent.getSource() .getException()); assertNull(tmpFile); assertFalse(socketInReadWrapper.inRead); socketEvents.validateOneSocketCreatedAndClosed(); } finally { server.shutdown(); } } private static void writeLines(ResponseWriter w, String[] lines) throws IOException { for (int i = 0; i < lines.length; i++) { w.println(lines[i]); } } public void testReceiveFile_cancelTransferJob() throws Exception { if (!CANCEL_SUPPORTED_ON_CONNECT) { trace("WARNING: Cancel not supported by this provider. testReceiveFile_cancelTransferJob cannot be used"); return; } final Object[] doCancel = new Object[1]; final IFileTransferListener listener = createFileTransferListener(); final FileTransferListenerWrapper lw = new FileTransferListenerWrapper( listener) { protected void handleStartConnectEvent( final IFileTransferConnectStartEvent event) { assertNotNull(event.getFileID()); assertNotNull(event.getFileID().getFilename()); FileTransferJob connectJob = event.prepareConnectJob(null); connectJob.addJobChangeListener(new JobChangeTraceListener( startTime)); event.connectUsingJob(connectJob); } protected void handleStartEvent( final IIncomingFileTransferReceiveStartEvent event) { spawnCancelThread(doCancel, new ICancelable() { public void cancel() { waitForSocketInRead(); assertNotNull(socketInReadWrapper); assertTrue(socketInReadWrapper.inRead); event.cancel(); } }); try { createTempFile(); event.receive(tmpFile); } catch (IOException e) { e.printStackTrace(); fail(e.toString()); } } }; final SimpleServer server = new SimpleServer(getName()); SimpleHttpServer simple = server.getSimpleHttpServer(); simple.setRequestHandler(new HttpRequestHandler() { public boolean processRequest(SimpleHttpServerConnection conn, SimpleRequest request) throws IOException { trace("Responding to request but never provide full body" + request.getRequestLine()); ResponseWriter w = conn.getWriter(); writeLines(w, new String[] { "HTTP/1.0 200 OK", "Content-Length: 9", "Content-Type: text/plain; charset=UTF-8", "" }); w.flush(); synchronized (doCancel) { doCancel[0] = Boolean.TRUE; } conn.setKeepAlive(true); // return stalledInRequestHandler(doCancel); } }); try { // path does not matter as server does not respond. testReceive(server.getServerURL() + "/foo", lw); assertHasEvent(startConnectEvents, IFileTransferConnectStartEvent.class); assertHasEvent(startEvents, IIncomingFileTransferReceiveStartEvent.class); assertDoneCancelled(); assertNotNull(tmpFile); assertTrue(tmpFile.exists()); assertEquals(0, tmpFile.length()); assertFalse(socketInReadWrapper.inRead); socketEvents.validateOneSocketCreatedAndClosed(); } finally { server.shutdown(); } } private void waitForSocketInRead() { assertNotNull(socketInReadWrapper); while (!socketInReadWrapper.inRead) { try { Thread.sleep(0); } catch (InterruptedException e) { } } assertTrue(socketInReadWrapper.inRead); } public void testReceiveFile_cancelTransferJobAfterOneBlock() throws Exception { if (!CANCEL_SUPPORTED_ON_CONNECT) { trace("WARNING: Cancel not supported by this provider. testReceiveFile_cancelTransferJobAfterOneBlock cannot be used"); return; } testReceiveFile_cancelTransferJobInMiddle( AbstractRetrieveFileTransfer.DEFAULT_BUF_LENGTH * 2, false); } public void testReceiveFile_cancelTransferJobInMiddle() throws Exception { if (!CANCEL_SUPPORTED_ON_CONNECT) { trace("WARNING: Cancel not supported by this provider. testReceiveFile_cancelTransferJobInMiddle cannot be used"); return; } testReceiveFile_cancelTransferJobInMiddle(20000, true); } public void testReceiveFile_cancelTransferJobInMiddle(final long len, final boolean expectedSocketInRead) throws Exception { if (!CANCEL_SUPPORTED_ON_CONNECT) { trace("WARNING: Cancel not supported by this provider. testReceiveFile_cancelTransferJobInMiddle cannot be used"); return; } final Object[] doCancel = new Object[1]; final IFileTransferListener listener = createFileTransferListener(); final FileTransferListenerWrapper lw = new FileTransferListenerWrapper( listener) { protected void handleStartConnectEvent( final IFileTransferConnectStartEvent event) { assertNotNull(event.getFileID()); assertNotNull(event.getFileID().getFilename()); FileTransferJob connectJob = event.prepareConnectJob(null); connectJob.addJobChangeListener(new JobChangeTraceListener( startTime)); event.connectUsingJob(connectJob); } protected void handleStartEvent( final IIncomingFileTransferReceiveStartEvent event) { spawnCancelThread(doCancel, new ICancelable() { public void cancel() { if (expectedSocketInRead) { waitForSocketInRead(); } event.cancel(); } }); try { createTempFile(); event.receive(tmpFile); } catch (IOException e) { e.printStackTrace(); fail(e.toString()); } } }; final SimpleServer server = new SimpleServer(getName()); SimpleHttpServer simple = server.getSimpleHttpServer(); simple.setRequestHandler(new HttpRequestHandler() { public boolean processRequest(SimpleHttpServerConnection conn, SimpleRequest request) throws IOException { trace("Responding to request but never provide only 50% of body" + request.getRequestLine()); ResponseWriter w = conn.getWriter(); writeLines(w, new String[] { "HTTP/1.0 200 OK", "Content-Length: " + len, "Content-Type: text/plain; charset=UTF-8", "" }); w.flush(); for (int i = 0; i < len / 2; i++) { w.write("x"); } w.flush(); conn.setKeepAlive(true); try { // give it a bit of time to receive the data Thread.sleep(200); } catch (InterruptedException e) { } return stalledInRequestHandler(doCancel); } }); try { // path does not matter as server does not respond. testReceive(server.getServerURL() + "/foo", lw); assertHasEvent(startConnectEvents, IFileTransferConnectStartEvent.class); assertHasEvent(startEvents, IIncomingFileTransferReceiveStartEvent.class); assertHasMoreThanEventCount(dataEvents, IIncomingFileTransferReceiveDataEvent.class, 0); assertDoneCancelled(); assertNotNull(tmpFile); assertTrue(tmpFile.exists()); assertEquals(len / 2, tmpFile.length()); assertFalse(socketInReadWrapper.inRead); socketEvents.validateOneSocketCreatedAndClosed(); } finally { server.shutdown(); } } private boolean stalledInRequestHandler(final Object[] doCancel) { Exception ex = null; try { synchronized (doCancel) { doCancel[0] = Boolean.TRUE; doCancel.wait(); } } catch (InterruptedException e) { // expected on shutdown. ex = e; } trace("Leaving request handler"); assertTrue(ex instanceof InterruptedException); return false; } interface ICancelable { void cancel(); } private void spawnCancelThread(final Object[] doCancel, final ICancelable cancelable) { Thread t = new Thread(new Runnable() { public void run() { trace("Cancel runnable started"); while (true) { boolean cancel = false; synchronized (doCancel) { cancel = doCancel[0] != null; } if (cancel) { trace("Before calling cancel"); cancelable.cancel(); trace("After calling cancel"); break; } try { Thread.sleep(200); } catch (InterruptedException e) { break; } } trace("Cancel runnable ending"); } }); t.start(); } }