/* * 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.annotation; import java.util.HashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; import org.cometd.bayeux.Channel; import org.cometd.bayeux.ChannelId; import org.cometd.bayeux.Message; import org.cometd.bayeux.client.ClientSession; import org.cometd.bayeux.client.ClientSessionChannel; import org.cometd.client.BayeuxClient; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; public class ClientAnnotationProcessorTest extends AbstractClientServerTest { private BayeuxClient bayeuxClient; private ClientAnnotationProcessor processor; @Before public void init() { bayeuxClient = newBayeuxClient(); processor = new ClientAnnotationProcessor(bayeuxClient); } @After public void destroy() { disconnectBayeuxClient(bayeuxClient); } @Test public void testNull() throws Exception { boolean processed = processor.process(null); assertFalse(processed); } @Test public void testNonServiceAnnotatedClass() throws Exception { NonServiceAnnotatedService s = new NonServiceAnnotatedService(); boolean processed = processor.process(s); assertFalse(processed); assertNull(s.session); } public static class NonServiceAnnotatedService { @Session private ClientSession session; } @Test public void testInjectClientSessionOnField() throws Exception { InjectClientSessionOnFieldService s = new InjectClientSessionOnFieldService(); boolean processed = processor.process(s); assertTrue(processed); assertNotNull(s.session); } @Service public static class InjectClientSessionOnFieldService { @Session private ClientSession session; } @Test public void testInjectClientSessionOnMethod() throws Exception { InjectClientSessionOnMethodService s = new InjectClientSessionOnMethodService(); boolean processed = processor.process(s); assertTrue(processed); assertNotNull(s.session); } @Service public static class InjectClientSessionOnMethodService { private ClientSession session; @Session private void set(ClientSession session) { this.session = session; } } @Test public void testListenUnlisten() throws Exception { final AtomicReference<Message> handshakeRef = new AtomicReference<>(); final CountDownLatch handshakeLatch = new CountDownLatch(1); final AtomicReference<Message> connectRef = new AtomicReference<>(); final CountDownLatch connectLatch = new CountDownLatch(1); final AtomicReference<Message> disconnectRef = new AtomicReference<>(); final CountDownLatch disconnectLatch = new CountDownLatch(1); ListenUnlistenService s = new ListenUnlistenService(handshakeRef, handshakeLatch, connectRef, connectLatch, disconnectRef, disconnectLatch); boolean processed = processor.process(s); assertTrue(processed); bayeuxClient.handshake(); assertTrue(handshakeLatch.await(5, TimeUnit.SECONDS)); Message handshake = handshakeRef.get(); assertNotNull(handshake); assertTrue(handshake.isSuccessful()); assertTrue(connectLatch.await(5, TimeUnit.SECONDS)); Message connect = connectRef.get(); assertNotNull(connect); assertTrue(connect.isSuccessful()); processed = processor.deprocessCallbacks(s); assertTrue(processed); // Listener method must not be notified, since we have deconfigured bayeuxClient.disconnect(1000); } @Service public static class ListenUnlistenService { private final AtomicReference<Message> handshakeRef; private final CountDownLatch handshakeLatch; private final AtomicReference<Message> connectRef; private final CountDownLatch connectLatch; private final AtomicReference<Message> disconnectRef; private final CountDownLatch disconnectLatch; public ListenUnlistenService(AtomicReference<Message> handshakeRef, CountDownLatch handshakeLatch, AtomicReference<Message> connectRef, CountDownLatch connectLatch, AtomicReference<Message> disconnectRef, CountDownLatch disconnectLatch) { this.handshakeRef = handshakeRef; this.handshakeLatch = handshakeLatch; this.connectRef = connectRef; this.connectLatch = connectLatch; this.disconnectRef = disconnectRef; this.disconnectLatch = disconnectLatch; } @Listener(Channel.META_HANDSHAKE) public void metaHandshake(Message handshake) { handshakeRef.set(handshake); handshakeLatch.countDown(); } @Listener(Channel.META_CONNECT) public void metaConnect(Message connect) { connectRef.set(connect); connectLatch.countDown(); } @Listener(Channel.META_DISCONNECT) public void metaDisconnect(Message connect) { disconnectRef.set(connect); disconnectLatch.countDown(); } } @Test public void testSubscribeUnsubscribe() throws Exception { final AtomicReference<Message> messageRef = new AtomicReference<>(); final AtomicReference<CountDownLatch> messageLatch = new AtomicReference<>(new CountDownLatch(1)); SubscribeUnsubscribeService s = new SubscribeUnsubscribeService(messageRef, messageLatch); boolean processed = processor.process(s); assertTrue(processed); final CountDownLatch subscribeLatch = new CountDownLatch(1); bayeuxClient.getChannel(Channel.META_SUBSCRIBE).addListener(new ClientSessionChannel.MessageListener() { @Override public void onMessage(ClientSessionChannel channel, Message message) { subscribeLatch.countDown(); } }); bayeuxClient.handshake(); assertTrue(bayeuxClient.waitFor(5000, BayeuxClient.State.CONNECTED)); assertTrue(subscribeLatch.await(5, TimeUnit.SECONDS)); bayeuxClient.getChannel("/foo").publish(new HashMap<>()); assertTrue(messageLatch.get().await(5, TimeUnit.SECONDS)); final CountDownLatch unsubscribeLatch = new CountDownLatch(1); bayeuxClient.getChannel(Channel.META_UNSUBSCRIBE).addListener(new ClientSessionChannel.MessageListener() { @Override public void onMessage(ClientSessionChannel channel, Message message) { unsubscribeLatch.countDown(); } }); processor.deprocessCallbacks(s); assertTrue(unsubscribeLatch.await(5, TimeUnit.SECONDS)); messageLatch.set(new CountDownLatch(1)); bayeuxClient.getChannel("/foo").publish(new HashMap<>()); assertFalse(messageLatch.get().await(1, TimeUnit.SECONDS)); } @Service public static class SubscribeUnsubscribeService { private final AtomicReference<Message> messageRef; private final AtomicReference<CountDownLatch> messageLatch; public SubscribeUnsubscribeService(AtomicReference<Message> messageRef, AtomicReference<CountDownLatch> messageLatch) { this.messageRef = messageRef; this.messageLatch = messageLatch; } @Subscription("/foo") public void foo(Message message) { messageRef.set(message); messageLatch.get().countDown(); } } @Test public void testUsage() throws Exception { final CountDownLatch connectLatch = new CountDownLatch(1); final AtomicReference<CountDownLatch> messageLatch = new AtomicReference<>(); UsageService s = new UsageService(connectLatch, messageLatch); processor.process(s); assertTrue(s.initialized); assertFalse(s.connected); final CountDownLatch subscribeLatch = new CountDownLatch(1); bayeuxClient.getChannel(Channel.META_SUBSCRIBE).addListener(new ClientSessionChannel.MessageListener() { @Override public void onMessage(ClientSessionChannel channel, Message message) { subscribeLatch.countDown(); } }); bayeuxClient.handshake(); assertTrue(connectLatch.await(5, TimeUnit.SECONDS)); assertTrue(s.connected); assertTrue(subscribeLatch.await(5, TimeUnit.SECONDS)); messageLatch.set(new CountDownLatch(1)); bayeuxClient.getChannel("/foo").publish(new HashMap<>()); assertTrue(messageLatch.get().await(5, TimeUnit.SECONDS)); processor.deprocess(s); assertFalse(s.initialized); messageLatch.set(new CountDownLatch(1)); bayeuxClient.getChannel("/foo").publish(new HashMap<>()); assertFalse(messageLatch.get().await(1, TimeUnit.SECONDS)); } @Service public static class UsageService { private final CountDownLatch connectLatch; private final AtomicReference<CountDownLatch> messageLatch; private boolean initialized; private boolean connected; @Session private ClientSession session; public UsageService(CountDownLatch connectLatch, AtomicReference<CountDownLatch> messageLatch) { this.connectLatch = connectLatch; this.messageLatch = messageLatch; } @PostConstruct private void init() { initialized = true; } @PreDestroy private void destroy() { initialized = false; } @Listener(Channel.META_CONNECT) public void metaConnect(Message connect) { connected = connect.isSuccessful(); connectLatch.countDown(); } @Subscription("/foo") public void foo(Message message) { messageLatch.get().countDown(); } } @Test public void testInjectables() throws Exception { Injectable i = new DerivedInjectable(); InjectablesService s = new InjectablesService(); processor = new ClientAnnotationProcessor(bayeuxClient, i); boolean processed = processor.process(s); assertTrue(processed); assertSame(i, s.i); } class Injectable { } class DerivedInjectable extends Injectable { } @Service public static class InjectablesService { @Inject private Injectable i; } @Test public void testResubscribeOnRehandshake() throws Exception { AtomicReference<CountDownLatch> messageLatch = new AtomicReference<>(); ResubscribeOnRehandshakeService s = new ResubscribeOnRehandshakeService(messageLatch); boolean processed = processor.process(s); assertTrue(processed); final AtomicReference<CountDownLatch> subscribeLatch = new AtomicReference<>(new CountDownLatch(1)); bayeuxClient.getChannel(Channel.META_SUBSCRIBE).addListener(new ClientSessionChannel.MessageListener() { @Override public void onMessage(ClientSessionChannel channel, Message message) { subscribeLatch.get().countDown(); } }); bayeuxClient.handshake(); assertTrue(bayeuxClient.waitFor(1000, BayeuxClient.State.CONNECTED)); assertTrue(subscribeLatch.get().await(5, TimeUnit.SECONDS)); messageLatch.set(new CountDownLatch(1)); bayeuxClient.getChannel("/foo").publish("data1"); assertTrue(messageLatch.get().await(5, TimeUnit.SECONDS)); bayeuxClient.disconnect(); assertTrue(bayeuxClient.waitFor(1000, BayeuxClient.State.DISCONNECTED)); // Wait for the /meta/connect to return. Thread.sleep(1000); // Rehandshake subscribeLatch.set(new CountDownLatch(1)); bayeuxClient.handshake(); assertTrue(bayeuxClient.waitFor(1000, BayeuxClient.State.CONNECTED)); assertTrue(subscribeLatch.get().await(5, TimeUnit.SECONDS)); // Republish, it must have resubscribed messageLatch.set(new CountDownLatch(1)); bayeuxClient.getChannel("/foo").publish("data2"); assertTrue(messageLatch.get().await(5, TimeUnit.SECONDS)); bayeuxClient.disconnect(); assertTrue(bayeuxClient.waitFor(1000, BayeuxClient.State.DISCONNECTED)); boolean deprocessed = processor.deprocess(s); assertTrue(deprocessed); // Wait for the /meta/connect to return. Thread.sleep(1000); // Rehandshake bayeuxClient.handshake(); assertTrue(bayeuxClient.waitFor(1000, BayeuxClient.State.CONNECTED)); // Republish, it must not have resubscribed messageLatch.set(new CountDownLatch(1)); bayeuxClient.getChannel("/foo").publish(new HashMap()); assertFalse(messageLatch.get().await(1, TimeUnit.SECONDS)); } @Service public static class ResubscribeOnRehandshakeService { private final AtomicReference<CountDownLatch> messageLatch; public ResubscribeOnRehandshakeService(AtomicReference<CountDownLatch> messageLatch) { this.messageLatch = messageLatch; } @Subscription("/foo") public void foo(Message message) { messageLatch.get().countDown(); } } @Test public void testListenerWithParameters() throws Exception { // Wait for handshake and first connect. CountDownLatch latch = new CountDownLatch(2); ListenerWithParametersService s = new ListenerWithParametersService(latch); boolean processed = processor.process(s); assertTrue(processed); bayeuxClient.handshake(); assertTrue(bayeuxClient.waitFor(1000, BayeuxClient.State.CONNECTED)); assertTrue(latch.await(5, TimeUnit.SECONDS)); } @Service public static class ListenerWithParametersService { private final CountDownLatch latch; public ListenerWithParametersService(CountDownLatch latch) { this.latch = latch; } @Listener("/meta/{action}") public void meta(Message message, @Param("action") String action) { if ("handshake".equals(action) || "connect".equals(action)) { latch.countDown(); } } } @Test public void testSubscriberWithParameters() throws Exception { CountDownLatch latch = new CountDownLatch(1); String value1 = "v1"; String value2 = "v2"; SubscriberWithParametersService s = new SubscriberWithParametersService(latch, value1, value2); boolean processed = processor.process(s); assertTrue(processed); bayeuxClient.handshake(); assertTrue(bayeuxClient.waitFor(1000, BayeuxClient.State.CONNECTED)); String channel = "/a/" + value1 + "/" + value2 + "/d"; assertFalse(new ChannelId(SubscriberWithParametersService.CHANNEL).bind(new ChannelId(channel)).isEmpty()); bayeuxClient.getChannel(channel).publish("data"); assertTrue(latch.await(5, TimeUnit.SECONDS)); } @Service public static class SubscriberWithParametersService { public static final String CHANNEL = "/a/{b}/{c}/d"; private final CountDownLatch latch; private final String value1; private final String value2; public SubscriberWithParametersService(CountDownLatch latch, String value1, String value2) { this.latch = latch; this.value1 = value1; this.value2 = value2; } @Subscription(CHANNEL) public void service(Message message, @Param("b") String b, @Param("c") String c) { assertEquals(value1, b); assertEquals(value2, c); latch.countDown(); } } }