/*
 * Copyright (c) 2014, 2021 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.tyrus.tests.servlet.basic;

import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import jakarta.websocket.ClientEndpointConfig;
import jakarta.websocket.CloseReason;
import jakarta.websocket.DeploymentException;
import jakarta.websocket.Endpoint;
import jakarta.websocket.EndpointConfig;
import jakarta.websocket.MessageHandler;
import jakarta.websocket.Session;
import jakarta.websocket.server.ServerEndpoint;

import org.glassfish.tyrus.client.ClientManager;
import org.glassfish.tyrus.container.grizzly.client.GrizzlyClientContainer;
import org.glassfish.tyrus.server.Server;
import org.glassfish.tyrus.test.tools.TestContainer;

import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

/**
 * Primarily meant to test servlet integration, might be someday used for simple stress testing.
 * <p/>
 * Tests are executed from descendant classes, which must implement {@link #getScheme()} method. This is used to enable
 * testing with {@code ws} and {@code wss} schemes.
 *
 * @author Pavel Bucek
 */
public abstract class ServletTestBase extends TestContainer {

    private static final String CONTEXT_PATH = "/servlet-test";

    public ServletTestBase() {
        setContextPath(CONTEXT_PATH);
    }

    protected abstract String getScheme();

    @Test
    public void testPlainEchoShort() throws DeploymentException, InterruptedException, IOException {
        final Server server = startServer(PlainEchoEndpoint.class);

        final CountDownLatch messageLatch = new CountDownLatch(1);

        try {
            final ClientManager client = createClient();
            client.connectToServer(new Endpoint() {
                                       @Override
                                       public void onOpen(Session session, EndpointConfig EndpointConfig) {
                                           try {
                                               session.addMessageHandler(new MessageHandler.Whole<String>() {
                                                   @Override
                                                   public void onMessage(String message) {
                                                       assertEquals(message, "Do or do not, there is no try.");
                                                       messageLatch.countDown();
                                                   }
                                               });

                                               session.getBasicRemote().sendText("Do or do not, there is no try.");
                                           } catch (IOException e) {
                                               // do nothing
                                           }
                                       }
                                   }, ClientEndpointConfig.Builder.create().build(),
                                   getURI(PlainEchoEndpoint.class.getAnnotation(ServerEndpoint.class).value(),
                                          getScheme()));

            messageLatch.await(1, TimeUnit.SECONDS);
            assertEquals(0, messageLatch.getCount());
        } finally {
            stopServer(server);
        }
    }

    @Test
    public void testPlainEchoShort100() throws DeploymentException, InterruptedException, IOException {
        final Server server = startServer(PlainEchoEndpoint.class);

        final CountDownLatch messageLatch = new CountDownLatch(100);

        try {
            final ClientManager client = createClient();
            client.connectToServer(new Endpoint() {
                                       @Override
                                       public void onOpen(Session session, EndpointConfig EndpointConfig) {
                                           try {
                                               session.addMessageHandler(new MessageHandler.Whole<String>() {
                                                   @Override
                                                   public void onMessage(String message) {
                                                       assertEquals(message, "Do or do not, there is no try.");
                                                       messageLatch.countDown();
                                                   }
                                               });

                                               for (int i = 0; i < 100; i++) {
                                                   session.getBasicRemote().sendText("Do or do not, there is no try.");
                                               }
                                           } catch (IOException e) {
                                               // do nothing
                                           }
                                       }
                                   }, ClientEndpointConfig.Builder.create().build(),
                                   getURI(PlainEchoEndpoint.class.getAnnotation(ServerEndpoint.class).value(),
                                          getScheme()));

            messageLatch.await(20, TimeUnit.SECONDS);
            assertEquals(0, messageLatch.getCount());
        } finally {
            stopServer(server);
        }
    }

    @Test
    public void testPlainEchoShort10Sequence() throws DeploymentException, InterruptedException, IOException {
        final Server server = startServer(PlainEchoEndpoint.class);

        final CountDownLatch messageLatch = new CountDownLatch(10);

        try {
            for (int i = 0; i < 10; i++) {
                final ClientManager client = createClient();
                client.connectToServer(new Endpoint() {
                                           @Override
                                           public void onOpen(Session session, EndpointConfig EndpointConfig) {
                                               try {
                                                   session.addMessageHandler(new MessageHandler.Whole<String>() {
                                                       @Override
                                                       public void onMessage(String message) {
                                                           assertEquals(message, "Do or do not, there is no try.");
                                                           messageLatch.countDown();
                                                       }
                                                   });

                                                   session.getBasicRemote().sendText("Do or do not, there is no try.");
                                               } catch (IOException e) {
                                                   // do nothing
                                               }
                                           }
                                       }, ClientEndpointConfig.Builder.create().build(),
                                       getURI(PlainEchoEndpoint.class.getAnnotation(ServerEndpoint.class).value(),
                                              getScheme()));

                // TODO - remove when possible.
                Thread.sleep(100);
            }

            messageLatch.await(5, TimeUnit.SECONDS);
            assertEquals(0, messageLatch.getCount());
        } finally {
            stopServer(server);
        }
    }

    @Test
    public void testPlainEchoShort10SequenceReturnedSession() throws DeploymentException, InterruptedException,
            IOException {
        final Server server = startServer(PlainEchoEndpoint.class);

        final CountDownLatch messageLatch = new CountDownLatch(10);

        try {
            for (int i = 0; i < 10; i++) {
                final ClientManager client = createClient();
                Session session = client.connectToServer(new Endpoint() {
                    @Override
                    public void onOpen(Session session, EndpointConfig EndpointConfig) {
                        session.addMessageHandler(new MessageHandler.Whole<String>() {
                            @Override
                            public void onMessage(String message) {
                                assertEquals(message, "Do or do not, there is no try.");
                                messageLatch.countDown();
                            }
                        });
                    }
                }, ClientEndpointConfig.Builder.create().build(), getURI(PlainEchoEndpoint.class
                                                                                 .getAnnotation(ServerEndpoint.class)
                                                                                 .value(), getScheme()));

                session.getBasicRemote().sendText("Do or do not, there is no try.");
                // TODO - remove when possible.
                Thread.sleep(100);
            }

            messageLatch.await(5, TimeUnit.SECONDS);
            assertEquals(0, messageLatch.getCount());
        } finally {
            stopServer(server);
        }
    }

    /**
     * 10x10x10 bytes.
     */
    private static final String LONG_MESSAGE =
            "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
                    + "12345678901234567890123456789012345678901234567890123456789012345678901234567890"
                    + "12345678901234567890123456789012345678901234567890123456789012345678901234567890"
                    + "12345678901234567890123456789012345678901234567890123456789012345678901234567890"
                    + "12345678901234567890123456789012345678901234567890123456789012345678901234567890"
                    + "12345678901234567890123456789012345678901234567890123456789012345678901234567890"
                    + "12345678901234567890123456789012345678901234567890123456789012345678901234567890"
                    + "12345678901234567890123456789012345678901234567890123456789012345678901234567890"
                    + "12345678901234567890123456789012345678901234567890123456789012345678901234567890"
                    + "12345678901234567890123456789012345678901234567890123456789012345678901234567890"
                    + "12345678901234567890123456789012345678901234567890123456789012345678901234567890"
                    + "12345678901234567890123456789012345678901234567890123456789012345678901234567890"
                    + "123456789012345678901234567890";

    @Test
    public void testPlainEchoLong() throws DeploymentException, InterruptedException, IOException {
        final Server server = startServer(PlainEchoEndpoint.class);

        final CountDownLatch messageLatch = new CountDownLatch(1);

        try {
            final ClientManager client = createClient();
            client.connectToServer(new Endpoint() {
                                       @Override
                                       public void onOpen(Session session, EndpointConfig EndpointConfig) {
                                           try {
                                               session.addMessageHandler(new MessageHandler.Whole<String>() {
                                                   @Override
                                                   public void onMessage(String message) {
                                                       assertEquals(message, LONG_MESSAGE);
                                                       messageLatch.countDown();
                                                   }
                                               });

                                               session.getBasicRemote().sendText(LONG_MESSAGE);
                                           } catch (IOException e) {
                                               // do nothing
                                           }
                                       }
                                   }, ClientEndpointConfig.Builder.create().build(),
                                   getURI(PlainEchoEndpoint.class.getAnnotation(ServerEndpoint.class).value(),
                                          getScheme()));

            messageLatch.await(1, TimeUnit.SECONDS);
            assertEquals(0, messageLatch.getCount());
        } finally {
            stopServer(server);
        }
    }

    @Test
    public void testPlainEchoLong10() throws DeploymentException, InterruptedException, IOException {
        final Server server = startServer(PlainEchoEndpoint.class);

        final CountDownLatch messageLatch = new CountDownLatch(10);

        try {
            final ClientManager client = createClient();
            client.connectToServer(new Endpoint() {
                                       @Override
                                       public void onOpen(Session session, EndpointConfig EndpointConfig) {
                                           try {
                                               session.addMessageHandler(new MessageHandler.Whole<String>() {
                                                   @Override
                                                   public void onMessage(String message) {
                                                       assertEquals(message, LONG_MESSAGE);
                                                       messageLatch.countDown();
                                                   }
                                               });

                                               for (int i = 0; i < 10; i++) {
                                                   session.getBasicRemote().sendText(LONG_MESSAGE);
                                               }
                                           } catch (IOException e) {
                                               // do nothing
                                           }
                                       }
                                   }, ClientEndpointConfig.Builder.create().build(),
                                   getURI(PlainEchoEndpoint.class.getAnnotation(ServerEndpoint.class).value(),
                                          getScheme()));

            messageLatch.await(10, TimeUnit.SECONDS);
            assertEquals(0, messageLatch.getCount());
        } finally {
            stopServer(server);
        }
    }

    @Test
    public void testPlainEchoLong10Sequence() throws DeploymentException, InterruptedException, IOException {
        final Server server = startServer(PlainEchoEndpoint.class);

        final CountDownLatch messageLatch = new CountDownLatch(10);

        try {
            for (int i = 0; i < 10; i++) {
                final ClientManager client = createClient();
                client.connectToServer(new Endpoint() {
                                           @Override
                                           public void onOpen(Session session, EndpointConfig EndpointConfig) {
                                               try {
                                                   session.addMessageHandler(new MessageHandler.Whole<String>() {
                                                       @Override
                                                       public void onMessage(String message) {
                                                           assertEquals(message, LONG_MESSAGE);
                                                           messageLatch.countDown();
                                                       }
                                                   });

                                                   session.getBasicRemote().sendText(LONG_MESSAGE);
                                               } catch (IOException e) {
                                                   // do nothing
                                               }
                                           }
                                       }, ClientEndpointConfig.Builder.create().build(),
                                       getURI(PlainEchoEndpoint.class.getAnnotation(ServerEndpoint.class).value(),
                                              getScheme()));

                // TODO - remove when possible.
                Thread.sleep(300);
            }

            messageLatch.await(10, TimeUnit.SECONDS);
            assertEquals(0, messageLatch.getCount());
        } finally {
            stopServer(server);
        }
    }

    @Test
    public void testGetRequestURI() throws DeploymentException, InterruptedException, IOException {
        final Server server = startServer(RequestUriEndpoint.class);

        final CountDownLatch messageLatch = new CountDownLatch(1);

        try {
            final ClientManager client = createClient();

            URI uri = getURI(RequestUriEndpoint.class.getAnnotation(ServerEndpoint.class).value(), getScheme());
            uri = URI.create(uri.toString() + "?test=1;aaa");

            client.connectToServer(new Endpoint() {
                @Override
                public void onOpen(Session session, EndpointConfig EndpointConfig) {
                    try {
                        session.addMessageHandler(new MessageHandler.Whole<String>() {
                            @Override
                            public void onMessage(String message) {
                                assertTrue(message.endsWith("?test=1;aaa"));
                                messageLatch.countDown();
                            }
                        });

                        session.getBasicRemote().sendText("test");
                    } catch (IOException e) {
                        // do nothing
                    }
                }
            }, ClientEndpointConfig.Builder.create().build(), uri);

            messageLatch.await(1, TimeUnit.SECONDS);
            assertEquals(0, messageLatch.getCount());
        } finally {
            stopServer(server);
        }
    }

    @Test
    public void testOnOpenClose() throws DeploymentException, InterruptedException, IOException {
        final Server server = startServer(OnOpenCloseEndpoint.class);

        final CountDownLatch latch = new CountDownLatch(2);

        try {
            final ClientManager client = createClient();

            URI uri = getURI(OnOpenCloseEndpoint.class.getAnnotation(ServerEndpoint.class).value(), getScheme());

            client.connectToServer(new Endpoint() {
                @Override
                public void onOpen(Session session, EndpointConfig EndpointConfig) {
                    latch.countDown();
                }

                @Override
                public void onClose(Session session, CloseReason closeReason) {
                    latch.countDown();
                }
            }, ClientEndpointConfig.Builder.create().build(), uri);

            latch.await(3, TimeUnit.SECONDS);
            assertEquals(0, latch.getCount());
        } finally {
            stopServer(server);
        }
    }

    // "performance" test; 500 kB message is echoed 10 times.
    @Test
    public void testMultiEcho() throws IOException, DeploymentException, InterruptedException {
        final Server server = startServer(MultiEchoEndpoint.class);

        final int LENGTH = 587952;
        byte[] b = new byte[LENGTH];
        Arrays.fill(b, 0, LENGTH, (byte) 'a');

        final String text = new String(b);

        final CountDownLatch messageLatch = new CountDownLatch(10);

        try {
            final ClientManager client = createClient();
            final Session session = client.connectToServer(new Endpoint() {
                @Override
                public void onOpen(Session session, EndpointConfig EndpointConfig) {
                    session.addMessageHandler(new MessageHandler.Whole<String>() {
                        @Override
                        public void onMessage(String message) {
                            assertEquals(LENGTH, message.length());
                            messageLatch.countDown();
                        }
                    });
                }
            }, ClientEndpointConfig.Builder.create().build(), getURI(MultiEchoEndpoint.class
                                                                             .getAnnotation(ServerEndpoint.class)
                                                                             .value(), getScheme()));

            session.getBasicRemote().sendText(text);

            messageLatch.await(10, TimeUnit.SECONDS);
            assertEquals(0, messageLatch.getCount());
        } finally {
            stopServer(server);
        }
    }

    // "performance" test; 20 clients, endpoint broadcasts.
    @Test
    public void testTyrusBroadcastString() throws IOException, DeploymentException, InterruptedException {
        final Server server = startServer(TyrusBroadcastEndpoint.class);

        final int LENGTH = 587952;
        byte[] b = new byte[LENGTH];
        Arrays.fill(b, 0, LENGTH, (byte) 'a');

        final String text = new String(b);

        final CountDownLatch messageLatch = new CountDownLatch(800);
        final List<Session> sessions = new ArrayList<Session>(20);

        try {
            for (int i = 0; i < 20; i++) {
                final ClientManager client = createClient();
                final Session session = client.connectToServer(new Endpoint() {
                    @Override
                    public void onOpen(Session session, EndpointConfig EndpointConfig) {
                        session.addMessageHandler(new MessageHandler.Whole<String>() {
                            @Override
                            public void onMessage(String message) {
                                assertEquals(LENGTH, message.length());
                                System.out.println("### " + messageLatch.getCount());
                                messageLatch.countDown();
                            }
                        });
                    }
                }, ClientEndpointConfig.Builder.create().build(), getURI(TyrusBroadcastEndpoint.class
                                                                                 .getAnnotation(ServerEndpoint.class)
                                                                                 .value(), getScheme()));
                System.out.println("Client " + i + " connected.");
                sessions.add(session);
            }

            final long l = System.currentTimeMillis();
            for (Session s : sessions) {
                s.getBasicRemote().sendText(text);
                s.getBasicRemote().sendText(text);
            }

            messageLatch.await(100, TimeUnit.SECONDS);
            assertEquals(0, messageLatch.getCount());
            System.out.println("***** Tyrus broadcast - text ***** " + (System.currentTimeMillis() - l));
        } finally {
            for (Session session : sessions) {
                session.close();
            }

            stopServer(server);
        }
    }

    // "performance" test; 20 clients, endpoint broadcasts.
    @Test
    public void testTyrusBroadcastBinary() throws IOException, DeploymentException, InterruptedException {
        final Server server = startServer(TyrusBroadcastEndpoint.class);

        final int LENGTH = 587952;
        byte[] b = new byte[LENGTH];
        Arrays.fill(b, 0, LENGTH, (byte) 'a');

        final String text = new String(b);

        final CountDownLatch messageLatch = new CountDownLatch(800);
        final List<Session> sessions = new ArrayList<Session>(20);

        try {
            for (int i = 0; i < 20; i++) {
                final ClientManager client = createClient();
                final Session session = client.connectToServer(new Endpoint() {
                    @Override
                    public void onError(Session session, Throwable thr) {
                        thr.printStackTrace();
                    }

                    @Override
                    public void onOpen(Session session, EndpointConfig EndpointConfig) {
                        session.addMessageHandler(new MessageHandler.Whole<byte[]>() {
                            @Override
                            public void onMessage(byte[] message) {
                                assertEquals(LENGTH, message.length);
                                System.out.println("### " + messageLatch.getCount());
                                messageLatch.countDown();
                            }
                        });


                    }
                }, ClientEndpointConfig.Builder.create().build(), getURI(TyrusBroadcastEndpoint.class
                                                                                 .getAnnotation(ServerEndpoint.class)
                                                                                 .value(), getScheme()));
                System.out.println("Client " + i + " connected.");
                sessions.add(session);
            }

            final long l = System.currentTimeMillis();
            for (Session s : sessions) {
                s.getBasicRemote().sendBinary(ByteBuffer.wrap(text.getBytes()));
                s.getBasicRemote().sendBinary(ByteBuffer.wrap(text.getBytes()));
            }

            messageLatch.await(100, TimeUnit.SECONDS);
            assertEquals(0, messageLatch.getCount());
            System.out.println("***** Tyrus broadcast - binary ***** " + (System.currentTimeMillis() - l));
        } finally {
            for (Session session : sessions) {
                session.close();
            }

            stopServer(server);
        }
    }

    // "performance" test; 20 clients, endpoint broadcasts.
    @Test
    public void testWebSocketBroadcast() throws IOException, DeploymentException, InterruptedException {
        final Server server = startServer(WebSocketBroadcastEndpoint.class);

        final int LENGTH = 587952;
        byte[] b = new byte[LENGTH];
        Arrays.fill(b, 0, LENGTH, (byte) 'a');

        final String text = new String(b);

        final CountDownLatch messageLatch = new CountDownLatch(800);
        final List<Session> sessions = new ArrayList<Session>(20);

        try {
            for (int i = 0; i < 20; i++) {
                final ClientManager client = createClient();
                final Session session = client.connectToServer(new Endpoint() {
                    @Override
                    public void onOpen(Session session, EndpointConfig EndpointConfig) {
                        session.addMessageHandler(new MessageHandler.Whole<String>() {
                            @Override
                            public void onMessage(String message) {
                                assertEquals(LENGTH, message.length());
                                messageLatch.countDown();
                                System.out.println("### " + messageLatch.getCount());
                            }
                        });
                    }
                }, ClientEndpointConfig.Builder.create().build(), getURI(WebSocketBroadcastEndpoint.class
                                                                                 .getAnnotation(ServerEndpoint.class)
                                                                                 .value(), getScheme()));
                System.out.println("Client " + i + " connected.");
                sessions.add(session);
            }

            final long l = System.currentTimeMillis();
            for (Session s : sessions) {
                s.getBasicRemote().sendText(text);
                s.getBasicRemote().sendText(text);
            }

            messageLatch.await(60, TimeUnit.SECONDS);
            assertEquals(0, messageLatch.getCount());

            System.out.println("***** WebSocket broadcast ***** " + (System.currentTimeMillis() - l));

        } finally {
            for (Session session : sessions) {
                session.close();
            }

            stopServer(server);
        }
    }

    // "performance" test; 20 clients, endpoint broadcasts.
    @Test
    public void testTyrusBroadcastStringSharedClientContainer() throws IOException, DeploymentException,
            InterruptedException {
        final Server server = startServer(TyrusBroadcastEndpoint.class);

        final int LENGTH = 587952;
        byte[] b = new byte[LENGTH];
        Arrays.fill(b, 0, LENGTH, (byte) 'a');

        final String text = new String(b);

        final CountDownLatch messageLatch = new CountDownLatch(800);
        final List<Session> sessions = new ArrayList<Session>(20);

        try {
            for (int i = 0; i < 20; i++) {
                final ClientManager client = createClient();
                client.getProperties().put(GrizzlyClientContainer.SHARED_CONTAINER, true);
                final Session session = client.connectToServer(new Endpoint() {
                    @Override
                    public void onOpen(Session session, EndpointConfig EndpointConfig) {
                        session.addMessageHandler(new MessageHandler.Whole<String>() {
                            @Override
                            public void onMessage(String message) {
                                assertEquals(LENGTH, message.length());
                                System.out.println("### " + messageLatch.getCount());
                                messageLatch.countDown();
                            }
                        });
                    }
                }, ClientEndpointConfig.Builder.create().build(), getURI(TyrusBroadcastEndpoint.class
                                                                                 .getAnnotation(ServerEndpoint.class)
                                                                                 .value(), getScheme()));
                System.out.println("Client " + i + " connected.");
                sessions.add(session);
            }

            final long l = System.currentTimeMillis();
            for (Session s : sessions) {
                s.getBasicRemote().sendText(text);
                s.getBasicRemote().sendText(text);
            }

            messageLatch.await(100, TimeUnit.SECONDS);
            assertEquals(0, messageLatch.getCount());
            System.out.println("***** Tyrus broadcast - text ***** " + (System.currentTimeMillis() - l));
        } finally {
            for (Session session : sessions) {
                session.close();
            }

            stopServer(server);
        }
    }

    // "performance" test; 20 clients, endpoint broadcasts.
    @Test
    public void testTyrusBroadcastBinarySharedClientContainer() throws IOException, DeploymentException,
            InterruptedException {
        final Server server = startServer(TyrusBroadcastEndpoint.class);

        final int LENGTH = 587952;
        byte[] b = new byte[LENGTH];
        Arrays.fill(b, 0, LENGTH, (byte) 'a');

        final String text = new String(b);

        final CountDownLatch messageLatch = new CountDownLatch(800);
        final List<Session> sessions = new ArrayList<Session>(20);

        try {
            for (int i = 0; i < 20; i++) {
                final ClientManager client = createClient();
                client.getProperties().put(GrizzlyClientContainer.SHARED_CONTAINER, true);
                final Session session = client.connectToServer(new Endpoint() {
                    @Override
                    public void onOpen(Session session, EndpointConfig EndpointConfig) {
                        session.addMessageHandler(new MessageHandler.Whole<byte[]>() {
                            @Override
                            public void onMessage(byte[] message) {
                                assertEquals(LENGTH, message.length);
                                System.out.println("### " + messageLatch.getCount());
                                messageLatch.countDown();
                            }
                        });
                    }
                }, ClientEndpointConfig.Builder.create().build(), getURI(TyrusBroadcastEndpoint.class
                                                                                 .getAnnotation(ServerEndpoint.class)
                                                                                 .value(), getScheme()));
                System.out.println("Client " + i + " connected.");
                sessions.add(session);
            }

            final long l = System.currentTimeMillis();
            for (Session s : sessions) {
                s.getBasicRemote().sendBinary(ByteBuffer.wrap(text.getBytes()));
                s.getBasicRemote().sendBinary(ByteBuffer.wrap(text.getBytes()));
            }

            messageLatch.await(100, TimeUnit.SECONDS);
            assertEquals(0, messageLatch.getCount());
            System.out.println("***** Tyrus broadcast - binary ***** " + (System.currentTimeMillis() - l));
        } finally {
            for (Session session : sessions) {
                session.close();
            }

            stopServer(server);
        }
    }

    // "performance" test; 20 clients, endpoint broadcasts.
    @Test
    public void testWebSocketBroadcastSharedClientContainer() throws IOException, DeploymentException,
            InterruptedException {
        final Server server = startServer(WebSocketBroadcastEndpoint.class);

        final int LENGTH = 587952;
        byte[] b = new byte[LENGTH];
        Arrays.fill(b, 0, LENGTH, (byte) 'a');

        final String text = new String(b);

        final CountDownLatch messageLatch = new CountDownLatch(800);
        final List<Session> sessions = new ArrayList<Session>(20);

        try {
            for (int i = 0; i < 20; i++) {
                final ClientManager client = createClient();
                client.getProperties().put(GrizzlyClientContainer.SHARED_CONTAINER, true);
                final Session session = client.connectToServer(new Endpoint() {
                    @Override
                    public void onOpen(Session session, EndpointConfig EndpointConfig) {
                        session.addMessageHandler(new MessageHandler.Whole<String>() {
                            @Override
                            public void onMessage(String message) {
                                assertEquals(LENGTH, message.length());
                                messageLatch.countDown();
                                System.out.println("### " + messageLatch.getCount());
                            }
                        });
                    }
                }, ClientEndpointConfig.Builder.create().build(), getURI(WebSocketBroadcastEndpoint.class
                                                                                 .getAnnotation(ServerEndpoint.class)
                                                                                 .value(), getScheme()));
                System.out.println("Client " + i + " connected.");
                sessions.add(session);
            }

            final long l = System.currentTimeMillis();
            for (Session s : sessions) {
                s.getBasicRemote().sendText(text);
                s.getBasicRemote().sendText(text);
            }

            messageLatch.await(60, TimeUnit.SECONDS);
            assertEquals(0, messageLatch.getCount());

            System.out.println("***** WebSocket broadcast ***** " + (System.currentTimeMillis() - l));

        } finally {
            for (Session session : sessions) {
                session.close();
            }

            stopServer(server);
        }
    }
}
