/*
* Copyright (c) 2008-2017 the original author or authors.
*
* 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.
*/
package org.cometd.server;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.cometd.bayeux.Message;
import org.cometd.bayeux.client.ClientSession;
import org.cometd.bayeux.client.ClientSessionChannel;
import org.cometd.bayeux.server.BayeuxServer;
import org.cometd.bayeux.server.LocalSession;
import org.cometd.bayeux.server.ServerChannel;
import org.cometd.bayeux.server.ServerMessage;
import org.cometd.bayeux.server.ServerSession;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class MessageProcessingOrderTest {
private final BayeuxServerImpl _bayeux = new BayeuxServerImpl();
@Before
public void init() throws Exception {
_bayeux.start();
}
@After
public void destroy() throws Exception {
_bayeux.stop();
}
@Test
public void testProcessingOrderClientPublishBroadcast() throws Exception {
final Queue<String> events = new ConcurrentLinkedQueue<>();
LocalSession session0 = _bayeux.newLocalSession("s0");
session0.handshake();
LocalSession session1 = _bayeux.newLocalSession("s1");
session1.handshake();
String channelName = "/foo/bar";
// Client-side extension for the sender.
session0.addExtension(new ClientExtension(events, "0"));
// Client receiving the publish reply.
session0.getChannel(channelName).addListener(new ClientListener(events, "0"));
// Client-side extension for the subscriber.
session1.addExtension(new ClientExtension(events, "1"));
// Subscriber receiving the published message.
session1.getChannel(channelName).subscribe(new ClientListener(events, "1"));
// Server extension.
_bayeux.addExtension(new ServerExtension(events));
// Server-side session extension for the sender.
session0.getServerSession().addExtension(new ServerSessionExtension(events, "0"));
// Server-side session extension for the subscriber.
session1.getServerSession().addExtension(new ServerSessionExtension(events, "1"));
// Server-side channel message listener.
_bayeux.getChannel(channelName).addListener(new ServerListener(events));
// Server-side session message listener.
session0.getServerSession().addListener(new ServerSessionListener(events, "0"));
session1.getServerSession().addListener(new ServerSessionListener(events, "1"));
session0.getChannel(channelName).publish("data");
List<String> expected = Arrays.asList(
"0.cln.ext.snd",
"srv.ext.rcv",
"0.srv.ssn.ext.rcv",
"srv.chn.lst",
"srv.ext.snd",
"1.srv.ssn.ext.snd",
"1.srv.ssn.lst",
"1.cln.ext.rcv",
"1.cln.lst",
"srv.ext.snd.rpy",
"0.srv.ssn.ext.snd.rpy",
"0.cln.ext.rcv.rpy",
"0.cln.lst.rpy"
);
Assert.assertEquals(expected, new ArrayList<>(events));
}
@Test
public void testProcessingOrderClientPublishServiceServerDeliver() throws Exception {
final Queue<String> events = new ConcurrentLinkedQueue<>();
LocalSession client = _bayeux.newLocalSession("cln");
client.handshake();
final LocalSession service = _bayeux.newLocalSession("svc");
service.handshake();
final String channelName = "/service/foo";
client.addExtension(new ClientExtension(events, "0"));
client.getChannel(channelName).addListener(new ClientListener(events, "0"));
_bayeux.addExtension(new ServerExtension(events));
client.getServerSession().addExtension(new ServerSessionExtension(events, "0"));
service.getServerSession().addExtension(new ServerSessionExtension(events, "1"));
ServerChannel channel = _bayeux.createChannelIfAbsent(channelName).getReference();
channel.addListener(new ServerListener(events));
// This is how services are typically implemented
channel.addListener(new ServerChannel.MessageListener() {
@Override
public boolean onMessage(ServerSession session, ServerChannel channel, ServerMessage.Mutable message) {
events.offer("svc.chn.lst");
session.deliver(service, channelName, message.getData());
return true;
}
});
client.getServerSession().addListener(new ServerSessionListener(events, "0"));
service.getServerSession().addListener(new ServerSessionListener(events, "1"));
service.addExtension(new ClientExtension(events, "1"));
service.getChannel(channelName).subscribe(new ClientListener(events, "1"));
client.getChannel(channelName).publish("data");
List<String> expected = Arrays.asList(
"0.cln.ext.snd",
"srv.ext.rcv",
"0.srv.ssn.ext.rcv",
"srv.chn.lst",
"svc.chn.lst",
"srv.ext.snd",
"0.srv.ssn.ext.snd",
"0.srv.ssn.lst",
"0.cln.ext.rcv",
"0.cln.lst",
"srv.ext.snd.rpy",
"0.srv.ssn.ext.snd.rpy",
"0.cln.ext.rcv.rpy",
"0.cln.lst.rpy"
);
Assert.assertEquals(expected, new ArrayList<>(events));
}
@Test
public void testProcessingOrderClientPublishServiceServerPublish() throws Exception {
final Queue<String> events = new ConcurrentLinkedQueue<>();
LocalSession session0 = _bayeux.newLocalSession("s0");
session0.handshake();
LocalSession session1 = _bayeux.newLocalSession("s1");
session1.handshake();
final LocalSession service = _bayeux.newLocalSession("svc");
service.handshake();
String serviceChannel = "/service/foo";
final String broadcastChannel = "/foo";
session0.addExtension(new ClientExtension(events, "0"));
session0.getChannel(serviceChannel).addListener(new ClientListener(events, "0"));
session1.addExtension(new ClientExtension(events, "1"));
session1.getChannel(serviceChannel).addListener(new ClientListener(events, "1"));
session1.getChannel(broadcastChannel).subscribe(new ClientListener(events, "1"), new ClientCallback(events, "1.sub"));
_bayeux.addExtension(new ServerExtension(events));
session0.getServerSession().addExtension(new ServerSessionExtension(events, "0"));
session1.getServerSession().addExtension(new ServerSessionExtension(events, "1"));
service.getServerSession().addExtension(new ServerSessionExtension(events, "1"));
ServerChannel channel = _bayeux.createChannelIfAbsent(serviceChannel).getReference();
channel.addListener(new ServerListener(events));
// Services are typically implemented with a ServerChannel.MessageListener.
channel.addListener(new ServerChannel.MessageListener() {
@Override
public boolean onMessage(ServerSession session, ServerChannel channel, ServerMessage.Mutable message) {
events.offer("svc.chn.lst");
_bayeux.createChannelIfAbsent(broadcastChannel).getReference().publish(service, message.getData());
return true;
}
});
session0.getServerSession().addListener(new ServerSessionListener(events, "0"));
session1.getServerSession().addListener(new ServerSessionListener(events, "1"));
service.getServerSession().addListener(new ServerSessionListener(events, "1"));
service.addExtension(new ClientExtension(events, "1"));
service.getChannel(serviceChannel).subscribe(new ClientListener(events, "1"));
session0.getChannel(serviceChannel).publish("data", new ClientCallback(events, "0.pub"));
List<String> expected = Arrays.asList(
"1.sub.cln.cbk",
"0.cln.ext.snd",
"srv.ext.rcv",
"0.srv.ssn.ext.rcv",
"srv.chn.lst",
"svc.chn.lst",
"srv.ext.snd",
"1.srv.ssn.ext.snd",
"1.srv.ssn.lst",
"1.cln.ext.rcv",
"1.cln.lst",
"srv.ext.snd.rpy",
"0.srv.ssn.ext.snd.rpy",
"0.cln.ext.rcv.rpy",
"0.pub.cln.cbk",
"0.cln.lst.rpy"
);
Assert.assertEquals(expected, new ArrayList<>(events));
}
private static class ClientListener implements ClientSessionChannel.MessageListener {
private final Queue<String> events;
private final String id;
private ClientListener(Queue<String> events, String id) {
this.events = events;
this.id = id;
}
@Override
public void onMessage(ClientSessionChannel channel, Message message) {
if (message.isPublishReply()) {
events.offer(id + ".cln.lst.rpy");
} else {
events.offer(id + ".cln.lst");
}
}
}
private static class ClientExtension extends ClientSession.Extension.Adapter {
private final Queue<String> events;
private final String id;
private ClientExtension(Queue<String> events, String id) {
this.events = events;
this.id = id;
}
@Override
public boolean send(ClientSession session, Message.Mutable message) {
events.offer(id + ".cln.ext.snd");
return true;
}
@Override
public boolean rcv(ClientSession session, Message.Mutable message) {
if (message.isPublishReply()) {
events.offer(id + ".cln.ext.rcv.rpy");
} else {
events.offer(id + ".cln.ext.rcv");
}
return true;
}
}
private static class ClientCallback implements ClientSessionChannel.MessageListener {
private final Queue<String> events;
private final String id;
private ClientCallback(Queue<String> events, String id) {
this.events = events;
this.id = id;
}
@Override
public void onMessage(ClientSessionChannel channel, Message message) {
if (message.isMeta() || message.isPublishReply()) {
events.offer(id + ".cln.cbk");
}
}
}
private static class ServerExtension extends BayeuxServer.Extension.Adapter {
private final Queue<String> events;
private ServerExtension(Queue<String> events) {
this.events = events;
}
@Override
public boolean rcv(ServerSession from, ServerMessage.Mutable message) {
events.offer("srv.ext.rcv");
return true;
}
@Override
public boolean send(ServerSession from, ServerSession to, ServerMessage.Mutable message) {
if (message.isPublishReply()) {
events.offer("srv.ext.snd.rpy");
} else {
events.offer("srv.ext.snd");
}
return true;
}
}
private static class ServerSessionExtension extends ServerSession.Extension.Adapter {
private final Queue<String> events;
private final String id;
private ServerSessionExtension(Queue<String> events, String id) {
this.events = events;
this.id = id;
}
@Override
public boolean rcv(ServerSession session, ServerMessage.Mutable message) {
events.offer(id + ".srv.ssn.ext.rcv");
return true;
}
@Override
public ServerMessage send(ServerSession session, ServerMessage message) {
if (message.isPublishReply()) {
events.offer(id + ".srv.ssn.ext.snd.rpy");
} else {
events.offer(id + ".srv.ssn.ext.snd");
}
return message;
}
}
private static class ServerListener implements ServerChannel.MessageListener {
private final Queue<String> events;
private ServerListener(Queue<String> events) {
this.events = events;
}
@Override
public boolean onMessage(ServerSession from, ServerChannel channel, ServerMessage.Mutable message) {
events.offer("srv.chn.lst");
return true;
}
}
private static class ServerSessionListener implements ServerSession.MessageListener {
private final Queue<String> events;
private final String id;
private ServerSessionListener(Queue<String> events, String id) {
this.events = events;
this.id = id;
}
@Override
public boolean onMessage(ServerSession session, ServerSession sender, ServerMessage message) {
events.offer(id + ".srv.ssn.lst");
return true;
}
}
}