byps.test.TestRemoteStreams.java Source code

Java tutorial

Introduction

Here is the source code for byps.test.TestRemoteStreams.java

Source

package byps.test;

/* USE THIS FILE ACCORDING TO THE COPYRIGHT RULES IN LICENSE.TXT WHICH IS PART OF THE SOURCE CODE PACKAGE */
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import junit.framework.Assert;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import byps.BContentStream;
import byps.BContentStreamAsyncCallback;
import byps.BContentStreamWrapper;
import byps.BException;
import byps.BExceptionC;
import byps.BWire;
import byps.RemoteException;
import byps.http.HConstants;
import byps.test.api.BClient_Testser;
import byps.test.api.remote.RemoteStreams;

/**
 * Tests for interface functions with stream types. This test requires the
 * webapp bypstest-srv to be started.
 */
public class TestRemoteStreams {

    BClient_Testser client;
    RemoteStreams remote;
    private Log log = LogFactory.getLog(TestRemoteStreams.class);

    @Before
    public void setUp() throws RemoteException {
        client = TestUtilsHttp.createClient();
        remote = client.getRemoteStreams();
    }

    @After
    public void tearDown() throws InterruptedException {
        if (client != null) {
            client.done();
        }
    }

    /**
     * Send and receive a stream with content length information.
     * 
     * @throws InterruptedException
     * @throws IOException
     */
    @Test
    public void testRemoteStreamsOneStreamContentLength() throws InterruptedException, IOException {
        log.info("testRemoteStreamsOneStreamContentLength(");

        String str = "hello";
        InputStream istrm = new ByteArrayInputStream(str.getBytes());
        remote.setImage(istrm);

        InputStream istrmR = remote.getImage();
        ByteBuffer buf = BWire.bufferFromStream(istrmR);
        String strR = new String(buf.array(), buf.position(), buf.remaining());
        TestUtils.assertEquals(log, "stream", str, strR);

        remote.setImage(null);
        TestUtils.checkTempDirEmpty(client);

        log.info(")testRemoteStreamsOneStreamContentLength");
    }

    /**
     * Send file stream.
     * A file stream has the fileName property set.
     * @throws InterruptedException
     * @throws IOException
     */
    @Test
    public void testRemoteStreamsFileStream() throws InterruptedException, IOException {
        log.info("testRemoteStreamsFileStream(");

        File file = File.createTempFile("byps", ".txt");
        String str = "hello";

        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file);
            fos.write(str.getBytes());
        } finally {
            if (fos != null)
                fos.close();
        }

        InputStream istrm = new BContentStreamWrapper(file);
        remote.setImage(istrm);

        BContentStream istrmR = (BContentStream) remote.getImage();

        TestUtils.assertEquals(log, "Content-Type", "text/plain", istrmR.getContentType());
        TestUtils.assertEquals(log, "Content-Length", file.length(), istrmR.getContentLength());
        TestUtils.assertEquals(log, "FileName", file.getName(), istrmR.getFileName());

        ByteBuffer buf = BWire.bufferFromStream(istrmR);
        String strR = new String(buf.array(), buf.position(), buf.remaining());
        TestUtils.assertEquals(log, "stream", str, strR);

        TestUtils.assertEquals(log, "Content-Type", "text/plain", istrmR.getContentType());
        TestUtils.assertEquals(log, "Content-Length", file.length(), istrmR.getContentLength());
        TestUtils.assertEquals(log, "FileName", file.getName(), istrmR.getFileName());

        remote.setImage(null);
        TestUtils.checkTempDirEmpty(client);

        log.info(")testRemoteStreamsFileStream");
    }

    /**
     * Send and receive a stream without content length information
     * 
     * @throws InterruptedException
     * @throws IOException
     */
    @Test
    public void testRemoteStreamsOneStreamChunked() throws InterruptedException, IOException {
        log.info("testRemoteStreamsOneStreamChunked(");

        String str = "hello";
        final ByteArrayInputStream bis = new ByteArrayInputStream(str.getBytes());
        InputStream istrm = new BContentStream() {

            @Override
            public long getContentLength() {
                return -1L;
            }

            @Override
            public int read() throws IOException {
                return bis.read();
            }

        };

        remote.setImage(istrm);

        InputStream istrmR = remote.getImage();
        ByteBuffer buf = BWire.bufferFromStream(istrmR);
        String strR = new String(buf.array(), buf.position(), buf.remaining());
        TestUtils.assertEquals(log, "stream", str, strR);

        remote.setImage(null);
        TestUtils.checkTempDirEmpty(client);

        log.info(")testRemoteStreamsOneStreamChunked");
    }

    /**
     * Send and receive many streams.
     * 
     * @throws InterruptedException
     * @throws IOException
     */
    @Test
    public void testRemoteStreamsManyStreams() throws InterruptedException, IOException {
        log.info("testRemoteStreamsManyStreams(");
        int nbOfStreams = 10;

        for (int i = 0; i < 10; i++) {
            internalTestRemoteStreamsManyStreams(nbOfStreams);
        }

        log.info(")testRemoteStreamsManyStreams");
    }

    private void internalTestRemoteStreamsManyStreams(int nbOfStreams) throws InterruptedException, IOException {
        Map<Integer, InputStream> streams = new TreeMap<Integer, InputStream>();
        Map<Integer, byte[]> streamBytes = new TreeMap<Integer, byte[]>();
        for (int i = 0; i < nbOfStreams; i++) {

            byte[] bytes = new byte[1];
            ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
            streamBytes.put(i, bytes);

            InputStream istrm = new BContentStreamWrapper(bis, "application/octet-stream", bytes.length);

            streams.put(i, istrm);
        }
        remote.setImages(streams, -1);
        TreeMap<Integer, InputStream> istrmsR = remote.getImages();

        TestUtils.assertEquals(log, "streams", streams, istrmsR); // Does not
                                                                  // compare
                                                                  // streams.
        for (int i = 0; i < nbOfStreams; i++) {
            InputStream istrm = new ByteArrayInputStream(streamBytes.get(i));
            InputStream istrmR = istrmsR.get(i);
            TestUtils.assertEquals(log, "stream-" + i, istrm, istrmR);
            istrmR.close();
        }
    }

    /**
     * Wrapper class for ByteArrayInputStream. - helps to check that all streams
     * are closed after the request - helps to check that an Exception thrown in
     * read() is correctly handled.
     */
    private class MyInputStream extends BContentStream {

        private volatile boolean isClosed;
        boolean throwEx;
        boolean throwError;
        byte[] buf;
        int idx;

        /**
         * Constructor
         * 
         * @param buf
         *          Buffer to read from
         * @param throwEx
         *          true, if an IOException has to be thrown
         * @param throwError
         *          true, if an IllegalStateException has to be thrown
         */
        public MyInputStream(byte[] buf, boolean throwEx, boolean throwError) {
            this.buf = buf;
            this.throwEx = throwEx;
            this.throwError = throwError;
        }

        @Override
        public void close() throws IOException {
            isClosed = true;
            super.close();
        }

        @Override
        public int read() throws IOException {
            if (throwEx) {
                log.info("throw IOException");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                }
                throw new IOException("Test Exception");
            }
            if (throwError) {
                log.info("throw IllegalStateException");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                }
                throw new IllegalStateException("Test Error");
            }
            if (idx < buf.length) {
                return ((int) buf[idx++]) & 0xFF;
            }
            return -1;
        }

        @Override
        public String getContentType() {
            return null;
        }

        @Override
        public long getContentLength() {
            return buf.length;
        }
    }

    /**
     * All the input streams sent are closed by the transport layer. Streams not
     * requested from the server must be closed too.
     * 
     * @throws InterruptedException
     * @throws BException
     */
    @Test
    public void testRemoteStreamsCloseStreamAfterSend() throws RemoteException {
        log.info("testRemoteStreamsCloseStreamAfterSend(");
        int count = 1;

        for (int i = 0; i < count; i++) {
            internalTestRemoteStreamsClosed();

            String logLine = "testRemoteStreamsCloseStreamAfterSend-" + i;
            client.getTransport().getWire().getTestAdapter().printServerLog(logLine);
        }

        log.info(")testRemoteStreamsCloseStreamAfterSend");
    }

    protected void internalTestRemoteStreamsClosed() throws RemoteException {
        if (log.isDebugEnabled())
            log.debug("internaltestRemoteStreamsClosed(");
        int nbOfStreams = 10;
        Map<Integer, InputStream> mystreams = new TreeMap<Integer, InputStream>();
        for (int i = 0; i < nbOfStreams; i++) {
            final String str = "" + i;
            byte[] buf = str.getBytes();
            MyInputStream mystrm = new MyInputStream(buf, false, false) {
                public String getContentType() {
                    return "strm-" + str;
                }
            };
            mystreams.put(i, mystrm);
        }

        if (log.isDebugEnabled())
            log.debug("setImages...");
        remote.setImages(mystreams, 1);
        if (log.isDebugEnabled())
            log.debug("setImages OK");

        // Stream at index 1 has not been read by the server.
        // The internal cleanup thread has to read the stream and close it 
        // after the message is sent.
        // Now wait for at most HConstants.CLEANUP_MILLIS until HActiveMessages.cleanup()
        // closes the stream and returns a response for this stream.
        // Then, the client will return from remote.setImages().

        for (int i = 0; i < nbOfStreams; i++) {
            MyInputStream istrm = (MyInputStream) mystreams.get(i);
            TestUtils.assertEquals(log, "InputStream[" + i + "].isClosed, istrm=" + istrm, true, istrm.isClosed);
        }
        if (log.isDebugEnabled())
            log.debug(")internaltestRemoteStreamsClosed");
    }

    /**
     * Handle an Exception thrown in InputStream.read correctly. The Exception
     * must be encapsulated in an BException. It must be passed to the caller. All
     * streams have to be closed. The server must have received an exception too.
     * This function tests the handling of an IOException and
     * IllegalStateException. All messages must have been finished after a while
     * when the client connection has been closed.
     * 
     * @throws InterruptedException
     * @throws IOException
     * @see MyInputStream
     */
    @Test
    public void testRemoteStreamsThrowExceptionOnRead() throws InterruptedException, IOException {
        log.info("testRemoteStreamsThrowExceptionOnRead(");

        for (int i = 0; i < 1; i++) {
            internalTestThrowExOnRead(true, false);
            internalTestThrowExOnRead(false, true);
        }

        client.getTransport().getWire().getTestAdapter().cancelAllRequests();

        try {
            // There should be no active messages on the server after the client side
            // has been finished.
            long[] messageIds = null;
            int keepMessageSeconds = ((int) HConstants.KEEP_MESSAGE_AFTER_FINISHED / 1000);
            int cleanupSeconds = (int) HConstants.CLEANUP_MILLIS / 1000;
            int waitUntilMessagesExpired = 2 * Math.max(cleanupSeconds, keepMessageSeconds) + 1;
            for (int retry = 0; retry < waitUntilMessagesExpired; retry++) {
                messageIds = client.getTransport().getWire().getTestAdapter().getAcitveMessagesOnServer(false);
                if (messageIds.length == 0)
                    break;
                Thread.sleep(1000);
            }
            TestUtils.assertEquals(log, "active messages: ", new long[0], messageIds);
        } finally {
            client.done();
            client = null;
        }
        log.info(")testRemoteStreamsThrowExceptionOnRead");

    }

    private void internalTestThrowExOnRead(boolean throwEx, boolean throwError)
            throws InterruptedException, IOException {
        log.info("internalTestThrowExOnRead(throwEx=" + throwEx + ", throwError=" + throwError);

        Map<Integer, InputStream> streams = new TreeMap<Integer, InputStream>();
        for (int i = 0; i < 3; i++) {
            String str = "hello-" + i;
            byte[] buf = str.getBytes();
            InputStream istrm = new MyInputStream(buf, i == 1 && throwEx, i == 1 && throwError);
            streams.put(i, istrm);
            log.info("strm[" + i + "]=" + str);
        }

        // An exception is thrown in HWireClient.
        try {
            log.info("setImages...");
            remote.setImages(streams, -1);
            Assert.fail("Exception expected");

            // The server waits for the stream in HActiveMessages.getIncomingOrOutgoingStream().
            // But the stream is never sent due to the text exception.
            // The client handles the exception by sending a cancel message.
            // This message interrupts the server. It needs at most HConstants.CLEANUP_MILLIS
            // until all streams of the message are internally released. 
            // Then the remote.setImages() will return.

        } catch (BException e) {
            log.info("setImages ex=" + e + ", OK");

            // The exception is an IOERROR, if the exception thrown in the stream is
            // received first.
            // This exception cancels the message and it might happen, that we receive
            // the CANCELLED
            // exception from first.
            TestUtils.assertTrue(log, "Exception Code",
                    e.code == BExceptionC.IOERROR || e.code == BExceptionC.CANCELLED);
        }

        // All streams must have been closed
        log.info("streams must be closed");
        int nbOfClosed = 0;
        int keepMessageSeconds = ((int) HConstants.KEEP_MESSAGE_AFTER_FINISHED / 1000) + 1;
        for (int retry = 0; retry < 10 * keepMessageSeconds; retry++) {
            for (int i = 0; i < streams.size(); i++) {
                MyInputStream istrm = (MyInputStream) streams.get(i);
                log.info("InputStream[" + i + "].isClosed=" + istrm.isClosed);
                if (istrm.isClosed)
                    nbOfClosed++;
            }
            if (nbOfClosed == streams.size())
                break;
            nbOfClosed = 0;
            Thread.sleep(100);
        }
        for (int i = 0; i < streams.size(); i++) {
            MyInputStream istrm = (MyInputStream) streams.get(i);
            TestUtils.assertEquals(log, "InputStream[" + i + "].isClosed, stream=" + istrm, true, istrm.isClosed);
        }

        log.info(")internalTestThrowExOnRead");
    }

    /**
     * Test for cloning streams inside the server. The server can read a stream as
     * long as the request (message with ByteBuffer) is not finished. If streams
     * have to be stored in member variables of the server objects, they have to
     * be cloned.
     * 
     * @throws InterruptedException
     * @throws IOException
     */
    @Test
    public void testRemoteStreamsCloneStream() throws InterruptedException, IOException {
        log.info("testRemoteStreamsCloneStream(");

        ArrayList<InputStream> streams = TestUtilsHttp.makeTestStreams();
        ArrayList<InputStream> streams2 = TestUtilsHttp.makeTestStreams();
        for (int i = 0; i < streams.size(); i++) {
            internalTestCloneStream(streams.get(i), streams2.get(i));
        }

        log.info(")testRemoteStreamsCloneStream");
    }

    protected void internalTestCloneStream(InputStream istrm, InputStream istrm2)
            throws BException, InterruptedException, IOException {

        log.info("internalTestCloneStream(" + istrm);

        // setImage clones the stream for reuse
        remote.setImage(istrm);

        log.info("start download");
        InputStream istrmR = remote.getImage();

        log.info("compare streams");
        TestUtils.assertEquals(log, "stream", istrm2, istrmR);

        remote.setImage(null);
        TestUtils.checkTempDirEmpty(client);

        log.info(")internalTestCloneStream");
    }

    /**
     * Check receiving streams asynchronously.
     * @throws IOException 
     * 
     */
    @Test
    public void testRemoteStreamsAsyncCallback() throws IOException {
        log.info("testRemoteStreamsAsyncCallback(");

        final String text = "abcdef";
        final CountDownLatch waitForFinished = new CountDownLatch(4);

        remote.setImage(new ByteArrayInputStream(text.getBytes()));

        log.info("start download");
        InputStream istrmR = remote.getImage();
        BContentStream.assignAsyncCallback(istrmR, new BContentStreamAsyncCallback() {

            @Override
            public boolean onReceivedData(byte[] buf, int len) {
                TestUtils.assertEquals(log, "stream content", text, new String(buf, 0, len));
                waitForFinished.countDown();
                return true;
            }

            @Override
            public void onReceivedContentLength(long contentLength) {
                TestUtils.assertEquals(log, "stream length", text.length(), (int) contentLength);
                waitForFinished.countDown();
            }

            @Override
            public void onReceivedContentType(String contentType) {
                TestUtils.assertEquals(log, "stream content type", "application/octet-stream", contentType);
                waitForFinished.countDown();
            }

            @Override
            public void onReceivedException(Throwable ex) {
                TestUtils.fail(log, "Exception " + ex);
            }

            @Override
            public void onFinished() {
                waitForFinished.countDown();
            }

        });

        try {
            waitForFinished.await(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
        }

        TestUtils.assertTrue(log, "Timeout", waitForFinished.getCount() == 0);

        log.info(")testRemoteStreamsAsyncCallback");
    }

    /**
     * A stream returned from the server can be passed as input parameter
     * to another call to the same server. The stream must not be downloaded/uploaded therefore.
     * @throws IOException 
     */
    @Test
    public void testHandoverStream() throws IOException {
        log.info("testHandoverStream(");

        ArrayList<InputStream> streams = TestUtilsHttp.makeTestStreams();

        for (int i = 0; i < streams.size(); i++) {

            InputStream istrm = streams.get(i);

            remote.setImage(istrm);

            InputStream strmFromServer = remote.getImage();
            BContentStream streamFailOpen = new BContentStreamWrapper((BContentStream) strmFromServer, 0L) {
                @Override
                protected InputStream openStream() throws IOException {
                    log.error("Stream must not be opened");
                    throw new IOException("Stream should be passed without beeing read.");
                }
            };

            remote.setImage(streamFailOpen);

            for (int j = 0; j < 2; j++) {
                ArrayList<InputStream> estreams = TestUtilsHttp.makeTestStreams();
                InputStream estrm = estreams.get(i);
                InputStream rstrm = remote.getImage();
                TestUtils.assertEquals(log, "stream[" + i + "]=" + estrm, estrm, rstrm);
            }

            remote.setImage(null);
            TestUtils.checkTempDirEmpty(client);
        }

        log.info(")testHandoverStream");
    }

    /**
     * Test with a stream class which properties are unavailable until the stream is opened.
     * @throws IOException 
     */
    @Test
    public void testStreamWithDeferedProperties() throws IOException {
        log.info("testStreamWithDeferedProperties(");

        BContentStream bstrm = (BContentStream) remote.getStreamDeferedProperies();
        TestUtils.assertEquals(log, "contentType", "", bstrm.getContentType());
        TestUtils.assertEquals(log, "contentLength", -1L, bstrm.getContentLength());
        TestUtils.assertEquals(log, "fileName", "", bstrm.getFileName());
        TestUtils.assertEquals(log, "attachmentCode", 0, bstrm.getAttachmentCode());

        bstrm.ensureProperties();
        TestUtils.assertEquals(log, "contentType", "application/mycontentype", bstrm.getContentType());
        TestUtils.assertEquals(log, "contentLength", 5L, bstrm.getContentLength());
        TestUtils.assertEquals(log, "fileName", "myfilename", bstrm.getFileName());
        TestUtils.assertEquals(log, "attachmentCode", BContentStream.ATTACHMENT, bstrm.getAttachmentCode());

        log.info(")testStreamWithDeferedProperties");
    }

    /**
     * Test with a stream class which properties are unavailable until the stream is opened.
     * The stream is passed back to the server and in turn read from the server. 
     * The stream properties must be unavailable until ensureProperties is called.
     * @throws IOException 
     */
    @Test
    public void testHandoverStreamWithDeferedProperties() throws IOException {
        log.info("testHandoverStreamWithDeferedProperties(");

        BContentStream bstrm1 = (BContentStream) remote.getStreamDeferedProperies();
        TestUtils.assertEquals(log, "contentType", "", bstrm1.getContentType());
        TestUtils.assertEquals(log, "contentLength", -1L, bstrm1.getContentLength());
        TestUtils.assertEquals(log, "fileName", "", bstrm1.getFileName());
        TestUtils.assertEquals(log, "attachmentCode", 0, bstrm1.getAttachmentCode());

        remote.setStreamDoNotMaterialize(bstrm1);
        BContentStream bstrm = (BContentStream) remote.getStreamDoNotClone();

        TestUtils.assertEquals(log, "contentType", "", bstrm.getContentType());
        TestUtils.assertEquals(log, "contentLength", -1L, bstrm.getContentLength());
        TestUtils.assertEquals(log, "fileName", "", bstrm.getFileName());
        TestUtils.assertEquals(log, "attachmentCode", 0, bstrm.getAttachmentCode());

        bstrm.ensureProperties();
        TestUtils.assertEquals(log, "contentType", "application/mycontentype", bstrm.getContentType());
        TestUtils.assertEquals(log, "contentLength", 5L, bstrm.getContentLength());
        TestUtils.assertEquals(log, "fileName", "myfilename", bstrm.getFileName());
        TestUtils.assertEquals(log, "attachmentCode", BContentStream.ATTACHMENT, bstrm.getAttachmentCode());

        log.info(")testHandoverStreamWithDeferedProperties");
    }

}