org.wisdom.framework.vertx.FileUploadTest.java Source code

Java tutorial

Introduction

Here is the source code for org.wisdom.framework.vertx.FileUploadTest.java

Source

/*
 * #%L
 * Wisdom-Framework
 * %%
 * Copyright (C) 2013 - 2014 Wisdom Framework
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package org.wisdom.framework.vertx;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.wisdom.api.Controller;
import org.wisdom.api.DefaultController;
import org.wisdom.api.configuration.ApplicationConfiguration;
import org.wisdom.api.content.ContentEngine;
import org.wisdom.api.exceptions.ExceptionMapper;
import org.wisdom.api.http.*;
import org.wisdom.api.router.Route;
import org.wisdom.api.router.RouteBuilder;
import org.wisdom.api.router.Router;
import org.wisdom.framework.vertx.file.DiskFileUpload;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
 * Check that we file upload works.
 */
public class FileUploadTest extends VertxBaseTest {

    private WisdomVertxServer server;

    @After
    public void tearDown() {
        if (server != null) {
            server.stop();
            server = null;
        }
    }

    @Test
    public void testFileUploadOfSmallFiles() throws InterruptedException, IOException {
        // Prepare the configuration
        ApplicationConfiguration configuration = mock(ApplicationConfiguration.class);
        when(configuration.getIntegerWithDefault(eq("vertx.http.port"), anyInt())).thenReturn(0);
        when(configuration.getIntegerWithDefault(eq("vertx.https.port"), anyInt())).thenReturn(-1);
        when(configuration.getIntegerWithDefault("vertx.acceptBacklog", -1)).thenReturn(-1);
        when(configuration.getIntegerWithDefault("vertx.receiveBufferSize", -1)).thenReturn(-1);
        when(configuration.getIntegerWithDefault("vertx.sendBufferSize", -1)).thenReturn(-1);
        when(configuration.getIntegerWithDefault("request.body.max.size", 100 * 1024)).thenReturn(100 * 1024);
        when(configuration.getLongWithDefault("http.upload.disk.threshold", DiskFileUpload.MINSIZE))
                .thenReturn(DiskFileUpload.MINSIZE);
        when(configuration.getLongWithDefault("http.upload.max", -1l)).thenReturn(-1l);
        when(configuration.getStringArray("wisdom.websocket.subprotocols")).thenReturn(new String[0]);
        when(configuration.getStringArray("vertx.websocket-subprotocols")).thenReturn(new String[0]);

        // Prepare the router with a controller
        Controller controller = new DefaultController() {
            @SuppressWarnings("unused")
            public Result index() throws IOException {
                FileItem item = context().file("upload");
                if (!item.isInMemory()) {
                    return badRequest("In memory expected");
                }
                if (!item.name().equals("my-file.dat")) {
                    return badRequest("broken name");
                }
                if (item.size() != 2048) {
                    return badRequest("broken file");
                }

                if (!context().form().get("comment").get(0).equals("my description")) {
                    return badRequest("broken form");
                }

                final File file = item.toFile();
                if (!file.exists() && file.length() != 2048) {
                    return badRequest("broken in memory to file handling");
                }

                return ok(item.stream()).as(MimeTypes.BINARY);
            }
        };
        Router router = mock(Router.class);
        Route route = new RouteBuilder().route(HttpMethod.POST).on("/").to(controller, "index");
        when(router.getRouteFor(anyString(), anyString(), any(Request.class))).thenReturn(route);

        ContentEngine contentEngine = getMockContentEngine();

        // Configure the server.
        server = new WisdomVertxServer();
        server.accessor = new ServiceAccessor(null, configuration, router, contentEngine, executor, null,
                Collections.<ExceptionMapper>emptyList());
        server.configuration = configuration;
        server.vertx = vertx;
        server.start();

        VertxHttpServerTest.waitForStart(server);

        // Now start bunch of clients
        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(NUMBER_OF_CLIENTS);

        int port = server.httpPort();

        for (int i = 1; i < NUMBER_OF_CLIENTS + 1; ++i) {
            // create and start threads
            clients.execute(new Client(startSignal, doneSignal, port, i, 2048));
        }

        startSignal.countDown(); // let all threads proceed
        if (!doneSignal.await(60, TimeUnit.SECONDS)) { // wait for all to finish
            Assert.fail("testFileUploadOfSmallFiles - Client not served in time");
        }

        assertThat(failure).isEmpty();
        assertThat(success).hasSize(NUMBER_OF_CLIENTS);
    }

    @Test
    public void testFileUploadOfSmallFilesOnDisk() throws InterruptedException, IOException {
        // Prepare the configuration
        ApplicationConfiguration configuration = mock(ApplicationConfiguration.class);
        when(configuration.getIntegerWithDefault(eq("vertx.http.port"), anyInt())).thenReturn(0);
        when(configuration.getIntegerWithDefault(eq("vertx.https.port"), anyInt())).thenReturn(-1);
        when(configuration.getIntegerWithDefault("vertx.acceptBacklog", -1)).thenReturn(-1);
        when(configuration.getIntegerWithDefault("vertx.receiveBufferSize", -1)).thenReturn(-1);
        when(configuration.getIntegerWithDefault("vertx.sendBufferSize", -1)).thenReturn(-1);
        when(configuration.getIntegerWithDefault("request.body.max.size", 100 * 1024)).thenReturn(100 * 1024);
        when(configuration.getStringArray("wisdom.websocket.subprotocols")).thenReturn(new String[0]);
        when(configuration.getStringArray("vertx.websocket-subprotocols")).thenReturn(new String[0]);
        // Reduce it to force disk storage
        when(configuration.getLongWithDefault("http.upload.disk.threshold", DiskFileUpload.MINSIZE))
                .thenReturn(1024l);
        when(configuration.getLongWithDefault("http.upload.max", -1l)).thenReturn(-1l);

        // Prepare the router with a controller
        Controller controller = new DefaultController() {
            @SuppressWarnings("unused")
            public Result index() throws IOException {
                FileItem item = context().file("upload");
                if (item.isInMemory()) {
                    return badRequest("on disk expected");
                }
                if (!item.name().equals("my-file.dat")) {
                    return badRequest("broken name");
                }
                if (item.size() != 2048) {
                    return badRequest("broken file");
                }

                if (!context().form().get("comment").get(0).equals("my description")) {
                    return badRequest("broken form");
                }

                final File file = item.toFile();
                if (!file.exists() && file.length() != 2048) {
                    return badRequest("broken file to file handling");
                }

                return ok(item.stream()).as(MimeTypes.BINARY);
            }
        };
        Router router = mock(Router.class);
        Route route = new RouteBuilder().route(HttpMethod.POST).on("/").to(controller, "index");
        when(router.getRouteFor(anyString(), anyString(), any(Request.class))).thenReturn(route);

        ContentEngine contentEngine = getMockContentEngine();

        // Configure the server.
        server = new WisdomVertxServer();
        server.accessor = new ServiceAccessor(null, configuration, router, contentEngine, executor, null,
                Collections.<ExceptionMapper>emptyList());
        server.vertx = vertx;
        server.configuration = configuration;
        server.start();

        VertxHttpServerTest.waitForStart(server);

        // Now start bunch of clients
        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(NUMBER_OF_CLIENTS);

        int port = server.httpPort();

        for (int i = 1; i < NUMBER_OF_CLIENTS + 1; ++i) {
            clients.submit(new Client(startSignal, doneSignal, port, i, 2048));
        }

        startSignal.countDown(); // let all threads proceed
        if (!doneSignal.await(120, TimeUnit.SECONDS)) { // wait for all to finish
            Assert.fail("testFileUploadOfSmallFilesOnDisk - Did not server all requests in time");
        }

        assertThat(failure).isEmpty();
        assertThat(success).hasSize(NUMBER_OF_CLIENTS);
    }

    @Test
    public void testFileUploadOfSmallFilesWithAsyncDownload() throws InterruptedException, IOException {
        // Prepare the configuration
        ApplicationConfiguration configuration = mock(ApplicationConfiguration.class);
        when(configuration.getIntegerWithDefault(eq("vertx.http.port"), anyInt())).thenReturn(0);
        when(configuration.getIntegerWithDefault(eq("vertx.https.port"), anyInt())).thenReturn(-1);
        when(configuration.getIntegerWithDefault("vertx.acceptBacklog", -1)).thenReturn(-1);
        when(configuration.getIntegerWithDefault("vertx.receiveBufferSize", -1)).thenReturn(-1);
        when(configuration.getIntegerWithDefault("vertx.sendBufferSize", -1)).thenReturn(-1);
        when(configuration.getIntegerWithDefault("request.body.max.size", 100 * 1024)).thenReturn(100 * 1024);
        when(configuration.getLongWithDefault("http.upload.disk.threshold", DiskFileUpload.MINSIZE))
                .thenReturn(DiskFileUpload.MINSIZE);
        when(configuration.getLongWithDefault("http.upload.max", -1l)).thenReturn(-1l);
        when(configuration.getStringArray("wisdom.websocket.subprotocols")).thenReturn(new String[0]);
        when(configuration.getStringArray("vertx.websocket-subprotocols")).thenReturn(new String[0]);

        // Prepare the router with a controller
        Controller controller = new DefaultController() {
            @SuppressWarnings("unused")
            public Result index() {
                final FileItem item = context().file("upload");
                if (!item.isInMemory()) {
                    return badRequest("In memory expected");
                }
                if (!item.name().equals("my-file.dat")) {
                    return badRequest("broken name");
                }
                if (item.size() != 2048) {
                    return badRequest("broken file");
                }

                if (!context().form().get("comment").get(0).equals("my description")) {
                    return badRequest("broken form");
                }

                return async(new Callable<Result>() {
                    @Override
                    public Result call() throws Exception {
                        return ok(item.stream()).as(MimeTypes.BINARY);
                    }
                });
            }
        };
        Router router = mock(Router.class);
        Route route = new RouteBuilder().route(HttpMethod.POST).on("/").to(controller, "index");
        when(router.getRouteFor(anyString(), anyString(), any(Request.class))).thenReturn(route);

        ContentEngine contentEngine = getMockContentEngine();

        // Configure the server.
        server = new WisdomVertxServer();
        server.accessor = new ServiceAccessor(null, configuration, router, contentEngine, executor, null,
                Collections.<ExceptionMapper>emptyList());
        server.configuration = configuration;
        server.vertx = vertx;
        server.start();

        VertxHttpServerTest.waitForStart(server);

        // Now start bunch of clients
        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(NUMBER_OF_CLIENTS);

        int port = server.httpPort();

        for (int i = 1; i < NUMBER_OF_CLIENTS + 1; ++i) // create and start threads
            clients.submit(new Client(startSignal, doneSignal, port, i, 2048));

        startSignal.countDown(); // let all threads proceed
        if (!doneSignal.await(60, TimeUnit.SECONDS)) { // wait for all to finish
            Assert.fail("testFileUploadOfSmallFilesWithAsyncDownload - Client not served in time");
        }
        assertThat(failure).isEmpty();
        assertThat(success).hasSize(NUMBER_OF_CLIENTS);
    }

    @Test
    public void testThatFileUpdateFailedWhenTheFileExceedTheConfiguredSize()
            throws InterruptedException, IOException {
        // Prepare the configuration
        ApplicationConfiguration configuration = mock(ApplicationConfiguration.class);
        when(configuration.getIntegerWithDefault(eq("vertx.http.port"), anyInt())).thenReturn(0);
        when(configuration.getIntegerWithDefault(eq("vertx.https.port"), anyInt())).thenReturn(-1);
        when(configuration.getIntegerWithDefault("vertx.acceptBacklog", -1)).thenReturn(-1);
        when(configuration.getIntegerWithDefault("vertx.receiveBufferSize", -1)).thenReturn(-1);
        when(configuration.getIntegerWithDefault("vertx.sendBufferSize", -1)).thenReturn(-1);
        when(configuration.getLongWithDefault("http.upload.disk.threshold", DiskFileUpload.MINSIZE))
                .thenReturn(DiskFileUpload.MINSIZE);
        when(configuration.getLongWithDefault("http.upload.max", -1l)).thenReturn(10l); // 10 bytes max
        when(configuration.getStringArray("wisdom.websocket.subprotocols")).thenReturn(new String[0]);
        when(configuration.getStringArray("vertx.websocket-subprotocols")).thenReturn(new String[0]);

        // Prepare the router with a controller
        Controller controller = new DefaultController() {
            @SuppressWarnings("unused")
            public Result index() {
                return ok();
            }
        };
        Router router = mock(Router.class);
        Route route = new RouteBuilder().route(HttpMethod.POST).on("/").to(controller, "index");
        when(router.getRouteFor(anyString(), anyString(), any(Request.class))).thenReturn(route);

        ContentEngine contentEngine = getMockContentEngine();

        // Configure the server.
        server = new WisdomVertxServer();
        server.accessor = new ServiceAccessor(null, configuration, router, contentEngine, executor, null,
                Collections.<ExceptionMapper>emptyList());
        server.configuration = configuration;
        server.vertx = vertx;
        server.start();

        VertxHttpServerTest.waitForStart(server);

        int port = server.httpPort();
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpPost post = new HttpPost("http://localhost:" + port + "/?id=" + 1);

        ByteArrayBody body = new ByteArrayBody("this is too much...".getBytes(), "my-file.dat");
        StringBody description = new StringBody("my description", ContentType.TEXT_PLAIN);
        HttpEntity entity = MultipartEntityBuilder.create().addPart("upload", body).addPart("comment", description)
                .build();

        post.setEntity(entity);

        CloseableHttpResponse response = httpclient.execute(post);
        // We should receive a Payload too large response (413)
        assertThat(response.getStatusLine().getStatusCode()).isEqualTo(413);

    }

    private class Client implements Runnable {
        private final CountDownLatch startSignal;
        private final CountDownLatch doneSignal;
        private final int port;
        private final int id;
        private final int size;

        Client(CountDownLatch startSignal, CountDownLatch doneSignal, int port, int id, int size) {
            this.startSignal = startSignal;
            this.doneSignal = doneSignal;
            this.port = port;
            this.id = id;
            this.size = size;
        }

        public void run() {
            try {
                startSignal.await();
                doWork();
            } catch (Throwable ex) {
                fail(id);
            } finally {
                doneSignal.countDown();
            }
        }

        void doWork() throws IOException {

            final byte[] data = new byte[size];
            RANDOM.nextBytes(data);

            CloseableHttpClient httpclient = null;
            CloseableHttpResponse response = null;
            try {
                httpclient = HttpClients.createDefault();
                HttpPost post = new HttpPost("http://localhost:" + port + "/?id=" + id);

                ByteArrayBody body = new ByteArrayBody(data, "my-file.dat");
                StringBody description = new StringBody("my description", ContentType.TEXT_PLAIN);
                HttpEntity entity = MultipartEntityBuilder.create().addPart("upload", body)
                        .addPart("comment", description).build();

                post.setEntity(entity);

                response = httpclient.execute(post);
                byte[] content = EntityUtils.toByteArray(response.getEntity());

                if (!isOk(response)) {
                    System.err.println("Invalid response code for " + id + " got "
                            + response.getStatusLine().getStatusCode() + " " + new String(content));
                    fail(id);
                    return;
                }

                if (!containsExactly(content, data)) {
                    System.err.println("Invalid content for " + id);
                    fail(id);
                    return;
                }

                success(id);

            } finally {
                IOUtils.closeQuietly(httpclient);
                IOUtils.closeQuietly(response);
            }
        }
    }

}