/**
* GRANITE DATA SERVICES
* Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S.
*
* This file is part of the Granite Data Services Platform.
*
* Granite Data Services is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* Granite Data Services is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA, or see <http://www.gnu.org/licenses/>.
*/
package org.granite.client.test.server;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.granite.client.messaging.Consumer;
import org.granite.client.messaging.Producer;
import org.granite.client.messaging.ResultIssuesResponseListener;
import org.granite.client.messaging.ServerApp;
import org.granite.client.messaging.TopicMessageListener;
import org.granite.client.messaging.channel.Channel;
//import org.granite.client.messaging.channel.Channel;
import org.granite.client.messaging.channel.ChannelFactory;
import org.granite.client.messaging.channel.MessagingChannel;
import org.granite.client.messaging.events.IssueEvent;
import org.granite.client.messaging.events.ResultEvent;
import org.granite.client.messaging.events.TopicMessageEvent;
import org.granite.client.messaging.messages.ResponseMessage;
import org.granite.client.messaging.transport.TransportException;
import org.granite.client.messaging.transport.TransportStatusHandler;
import org.granite.client.test.server.chat.ChatApplication;
import org.granite.logging.Logger;
import org.granite.test.container.EmbeddedContainer;
import org.granite.util.ContentType;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
/**
* Created by william on 30/09/13.
*/
@RunWith(Parameterized.class)
public class TestMessagingChat {
private static final Logger log = Logger.getLogger(TestMessagingChat.class);
@Parameterized.Parameters(name = "container: {0}, encoding: {1}, channel: {2}")
public static Iterable<Object[]> data() {
return ContainerTestUtil.data();
}
private ContentType contentType;
private String channelType;
protected static EmbeddedContainer container;
private static final ServerApp SERVER_APP_APP = new ServerApp("/chat", false, "localhost", 8787);
public TestMessagingChat(String containerClassName, ContentType contentType, String channelType) {
this.contentType = contentType;
this.channelType = channelType;
}
@BeforeClass
public static void startContainer() throws Exception {
// Build a chat server application
WebArchive war = ShrinkWrap.create(WebArchive.class, "chat.war");
war.addClass(ChatApplication.class);
container = ContainerTestUtil.newContainer(war, false);
container.start();
log.info("Container started");
}
@AfterClass
public static void stopContainer() throws Exception {
container.stop();
container.destroy();
log.info("Container stopped");
}
@Test
public void testTextProducer() throws Exception {
ChannelFactory channelFactory = ContainerTestUtil.buildChannelFactory(contentType);
MessagingChannel channel = channelFactory.newMessagingChannel(channelType, "messagingamf", SERVER_APP_APP);
Producer producer = new Producer(channel, "chat", "chat");
ResponseMessage message = producer.publish("test").get();
Assert.assertNotNull("Message has clientId", message.getClientId());
channel.stop();
channelFactory.stop();
}
private static final int MSG_COUNT = 500;
@Test
public void testChatTextSingleConsumer() throws Exception {
log.info("TestMessagingChat.testChatTextSingleConsumer %s - %s", channelType, contentType);
CyclicBarrier[] barriers = new CyclicBarrier[3];
barriers[0] = new CyclicBarrier(2);
barriers[1] = new CyclicBarrier(2);
barriers[2] = new CyclicBarrier(2);
String[] messages = new String[MSG_COUNT];
for (int i = 0; i < MSG_COUNT; i++)
messages[i] = (i+1) + "::" + UUID.randomUUID().toString();
ConsumerThread consumer = new ConsumerThread("C", messages, barriers);
consumer.start();
try {
barriers[0].await(5, TimeUnit.SECONDS);
log.info("Consumer subscribed");
}
catch (TimeoutException e) {
log.error(e, "Consumer subscription timeout");
Assert.fail("Consumer subscription failed");
}
ChannelFactory channelFactory = ContainerTestUtil.buildChannelFactory(contentType);
MessagingChannel channel = channelFactory.newMessagingChannel(channelType, "messagingamf", SERVER_APP_APP);
Producer producer = new Producer(channel, "chat", "chat");
for (int i = 0; i < MSG_COUNT; i++) {
log.info("Producer sent message " + messages[i]);
// TODO: Test does not always pass with a too small delay between message ???
Thread.sleep(5);
producer.publish(messages[i]);
}
try {
barriers[1].await(10, TimeUnit.SECONDS);
log.info("Consumer received messaged");
}
catch (TimeoutException e) {
log.error(e, "Consumer reception timeout");
Assert.fail("Consumer receive messages failed");
}
try {
barriers[2].await(10, TimeUnit.SECONDS);
log.info("Consumer unsubscribed and stopped");
channelFactory.stop();
}
catch (TimeoutException e) {
log.error(e, "Consumer unsubscription timeout");
Assert.fail("Consumer unsubscription failed");
}
channel.stop();
channelFactory.stop();
}
private static final int CONSUMER_COUNT = 5;
@Test
public void testChatTextMultiConsumer() throws Exception {
log.info("TestMessagingChat.testChatTextMultiConsumer %s - %s", channelType, contentType);
CyclicBarrier[] barriers = new CyclicBarrier[3];
barriers[0] = new CyclicBarrier(CONSUMER_COUNT+1);
barriers[1] = new CyclicBarrier(CONSUMER_COUNT+1);
barriers[2] = new CyclicBarrier(CONSUMER_COUNT+1);
String[] messages = new String[MSG_COUNT];
for (int i = 0; i < MSG_COUNT; i++)
messages[i] = (i+1) + "::" + UUID.randomUUID().toString();
for (int i = 0; i < CONSUMER_COUNT; i++) {
ConsumerThread consumer = new ConsumerThread("C" + (i+1), messages, barriers);
consumer.start();
}
try {
barriers[0].await(5, TimeUnit.SECONDS);
log.info("All consumers subscribed");
}
catch (TimeoutException e) {
log.error(e, "Consumers subscription timeout");
Assert.fail("Consumers not subscribed");
}
ChannelFactory channelFactory = ContainerTestUtil.buildChannelFactory(contentType);
MessagingChannel channel = channelFactory.newMessagingChannel(channelType, "messagingamf-P", SERVER_APP_APP);
Producer producer = new Producer(channel, "chat", "chat");
for (int i = 0; i < MSG_COUNT; i++) {
log.info("Producer sent message " + messages[i]);
// TODO: Test does not always pass with a too small delay between messages ???
Thread.sleep(5);
producer.publish(messages[i]);
}
log.info("All messages sent, wait for consumers");
boolean success = false;
try {
barriers[1].await(10, TimeUnit.SECONDS);
log.info("All messages received");
success = true;
}
catch (TimeoutException e) {
log.error(e, "Consumers reception timeout, wait for unsubcription/stop");
}
try {
barriers[2].await(10, TimeUnit.SECONDS);
log.info("All consumers unsubscribed and stopped");
Assert.assertTrue("All messages received by all consumers", success);
channelFactory.stop();
}
catch (TimeoutException e) {
log.error(e, "Consumers unsubscription timeout");
Assert.fail("Consumers unsubscription/stop failed");
}
}
protected void waitForChannel(Channel channel, CyclicBarrier barrier) {
try {
barrier.await();
}
catch (Exception e) {
log.error(e, "Error while releasing barrier %s for chanel %s", barrier, channel);
}
}
private class ConsumerThread implements Runnable {
private String id;
private List<String> messages;
private List<String> received = new ArrayList<String>();
private CyclicBarrier[] barriers;
private Thread thread = new Thread(this);
private Consumer consumer;
private int timeout = 20;
public ConsumerThread(String id, String[] messages, CyclicBarrier[] barriers) {
this.id = id;
thread.setName(id);
this.messages = Arrays.asList(messages);
this.barriers = barriers;
}
@SuppressWarnings("unused")
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public void start() {
thread.start();
}
private CountDownLatch waitToStop = new CountDownLatch(1);
@Override
public void run() {
ChannelFactory channelFactory = ContainerTestUtil.buildChannelFactory(contentType);
final MessagingChannel channel = channelFactory.newMessagingChannel(channelType, "messagingamf-" + id, SERVER_APP_APP);
channel.getTransport().setStatusHandler(new TransportStatusHandler() {
@Override
public void handleIO(boolean active) {
}
@Override
public void handleException(TransportException e) {
log.error(e, "Consumer %s transport exception", id);
}
});
consumer = new Consumer(channel, "chat", "chat");
consumer.addMessageListener(new ConsumerMessageListener());
consumer.subscribe(new ResultIssuesResponseListener() {
@Override
public void onResult(ResultEvent event) {
log.info("Consumer %s: subscribed %s - %s", id, event.getMessage().getClientId(), event.getResult());
waitForChannel(consumer.getChannel(), barriers[0]);
}
@Override
public void onIssue(IssueEvent event) {
log.error("Consumer %s: subscription failed %s", id, event.toString());
try {
barriers[0].await();
}
catch (Exception e) {
log.error(e, "Error while released subscription barrier: %s", channel);
}
}
});
try {
if (!waitToStop.await(timeout, TimeUnit.SECONDS))
log.error("Consumer %s time out", id);
}
catch (Exception e) {
log.error(e, "Consumer %s interrupted", id);
}
try {
log.info("Consumer %s stopping", id);
channel.stop();
channelFactory.stop();
barriers[2].await();
}
catch (Exception e) {
log.error(e, "Consumer %s did not terminate correctly", id);
}
}
private class ConsumerMessageListener implements TopicMessageListener {
@Override
public void onMessage(TopicMessageEvent event) {
if (messages.contains(event.getData())) {
received.add((String)event.getData());
log.info("Consumer %s: received message %s (%d)", id, event.getData(), received.size());
if (received.size() == messages.size() && received.containsAll(messages)) {
log.info("Consumer %s received all messages, wait for others", id);
try {
barriers[1].await();
}
catch (InterruptedException e) {
log.info(e, "Consumer %s interrupted waiting for others", id);
}
catch (BrokenBarrierException e) {
log.info(e, "Consumer %s interrupted waiting for others", id);
}
consumer.unsubscribe(new ResultIssuesResponseListener() {
@Override
public void onResult(ResultEvent event) {
log.info("Consumer %s: unsubscribed %s", id, event.getResult());
waitToStop.countDown();
}
@Override
public void onIssue(IssueEvent event) {
log.error("Consumer %s: unsubscription failed %s", id, event.toString());
waitToStop.countDown();
}
});
}
}
else
log.warn("Consumer %s: unexpected received message %s", id, event.getData());
}
}
}
}