/**
* Copyright 2007-2015, Kaazing Corporation. All rights reserved.
*
* 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.kaazing.k3po.driver.internal.netty.bootstrap.agrona;
import static java.lang.String.format;
import static java.lang.Thread.sleep;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.jboss.netty.buffer.ChannelBuffers.buffer;
import static org.jboss.netty.channel.Channels.pipeline;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.kaazing.k3po.driver.internal.netty.channel.ChannelAddressFactory.newChannelAddressFactory;
import static org.kaazing.k3po.driver.internal.netty.channel.Channels.flush;
import static org.kaazing.k3po.driver.internal.netty.channel.agrona.AgronaChannelAddress.OPTION_READER;
import static org.kaazing.k3po.driver.internal.netty.channel.agrona.AgronaChannelAddress.OPTION_WRITER;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.internal.verification.VerificationModeFactory.times;
import java.net.URI;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelEvent;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.WriteCompletionEvent;
import org.junit.Rule;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.rules.DisableOnDebug;
import org.junit.rules.TestRule;
import org.junit.rules.Timeout;
import org.junit.runner.RunWith;
import org.kaazing.k3po.driver.internal.netty.bootstrap.ClientBootstrapRule;
import org.kaazing.k3po.driver.internal.netty.channel.ChannelAddress;
import org.kaazing.k3po.driver.internal.netty.channel.ChannelAddressFactory;
import org.kaazing.k3po.driver.internal.netty.channel.FlushEvent;
import org.kaazing.k3po.driver.internal.netty.channel.SimpleChannelHandler;
import org.kaazing.k3po.driver.internal.netty.channel.agrona.BroadcastTransmitterChannelWriter;
import org.kaazing.k3po.driver.internal.netty.channel.agrona.ChannelReader;
import org.kaazing.k3po.driver.internal.netty.channel.agrona.ChannelWriter;
import org.kaazing.k3po.driver.internal.netty.channel.agrona.CopyBroadcastReceiverChannelReader;
import org.kaazing.k3po.driver.internal.netty.channel.agrona.RingBufferChannelReader;
import org.kaazing.k3po.driver.internal.netty.channel.agrona.RingBufferChannelWriter;
import org.mockito.InOrder;
import org.agrona.MutableDirectBuffer;
import org.agrona.concurrent.MessageHandler;
import org.agrona.concurrent.UnsafeBuffer;
import org.agrona.concurrent.broadcast.BroadcastBufferDescriptor;
import org.agrona.concurrent.broadcast.BroadcastReceiver;
import org.agrona.concurrent.broadcast.BroadcastTransmitter;
import org.agrona.concurrent.broadcast.CopyBroadcastReceiver;
import org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer;
import org.agrona.concurrent.ringbuffer.RingBufferDescriptor;
@RunWith(Theories.class)
public class AgronaClientBootstrapTest {
private static final int BUFFER_CAPACITY = 4096;
private static final int BROADCAST_BUFFER_TOTAL_LENGTH = BUFFER_CAPACITY + BroadcastBufferDescriptor.TRAILER_LENGTH;
private static final int RING_BUFFER_TOTAL_LENGTH = BUFFER_CAPACITY + RingBufferDescriptor.TRAILER_LENGTH;
private enum ReaderStrategy {
MANY_TO_ONE_RING_BUFFER, BROADCAST_RECEIVER
}
private enum WriterStrategy {
MANY_TO_ONE_RING_BUFFER, BROADCAST_TRANSMITTER
}
@DataPoints
public static final Set<ReaderStrategy> READER_STRATEGIES = EnumSet.allOf(ReaderStrategy.class);
@DataPoints
public static final Set<WriterStrategy> WRITER_STRATEGIES = EnumSet.allOf(WriterStrategy.class);
@Rule
public final ClientBootstrapRule bootstrap = new ClientBootstrapRule("agrona");
@Rule
public final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS));
@Theory
public void shouldConnectEchoThenClose(WriterStrategy pingStrategy, ReaderStrategy pongStrategy) throws Exception {
final AtomicInteger pongsReceived = new AtomicInteger();
SimpleChannelHandler client = new SimpleChannelHandler() {
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
pongsReceived.incrementAndGet();
super.messageReceived(ctx, e);
}
};
SimpleChannelHandler clientSpy = spy(client);
bootstrap.setPipeline(pipeline(clientSpy));
ChannelReader pingReader;
ChannelWriter pingWriter;
switch (pingStrategy) {
case MANY_TO_ONE_RING_BUFFER:
UnsafeBuffer manyToOnePingBuffer = new UnsafeBuffer(new byte[RING_BUFFER_TOTAL_LENGTH]);
pingReader = new RingBufferChannelReader(new ManyToOneRingBuffer(manyToOnePingBuffer));
pingWriter = new RingBufferChannelWriter(new ManyToOneRingBuffer(manyToOnePingBuffer));
break;
case BROADCAST_TRANSMITTER:
UnsafeBuffer broadcastPingBuffer = new UnsafeBuffer(new byte[BROADCAST_BUFFER_TOTAL_LENGTH]);
BroadcastReceiver receiver = new BroadcastReceiver(broadcastPingBuffer);
pingReader = new CopyBroadcastReceiverChannelReader(new CopyBroadcastReceiver(receiver));
pingWriter = new BroadcastTransmitterChannelWriter(new BroadcastTransmitter(broadcastPingBuffer));
break;
default:
throw new IllegalArgumentException(format("Unexpected writer strategy %s", pingStrategy));
}
ChannelReader pongReader;
ChannelWriter pongWriter;
switch (pongStrategy) {
case MANY_TO_ONE_RING_BUFFER:
UnsafeBuffer manyToOnePongBuffer = new UnsafeBuffer(new byte[RING_BUFFER_TOTAL_LENGTH]);
pongReader = new RingBufferChannelReader(new ManyToOneRingBuffer(manyToOnePongBuffer));
pongWriter = new RingBufferChannelWriter(new ManyToOneRingBuffer(manyToOnePongBuffer));
break;
case BROADCAST_RECEIVER:
UnsafeBuffer broadcastPongBuffer = new UnsafeBuffer(new byte[BROADCAST_BUFFER_TOTAL_LENGTH]);
BroadcastReceiver pongReceiver = new BroadcastReceiver(broadcastPongBuffer);
pongReader = new CopyBroadcastReceiverChannelReader(new CopyBroadcastReceiver(pongReceiver));
BroadcastTransmitter transmitter = new BroadcastTransmitter(broadcastPongBuffer);
pongWriter = new BroadcastTransmitterChannelWriter(transmitter);
break;
default:
throw new IllegalArgumentException(format("Unexpected reader strategy %s", pongStrategy));
}
ChannelAddressFactory channelAddressFactory = newChannelAddressFactory();
URI location = URI.create("agrona://stream/bidirectional");
Map<String, Object> options = new HashMap<>();
options.put(OPTION_READER, pongReader);
options.put(OPTION_WRITER, pingWriter);
ChannelAddress channelAddress = channelAddressFactory.newChannelAddress(location, options);
ChannelBuffer ping = buffer(256);
ping.writeInt(0x01);
ping.writeBytes("Hello, world".getBytes(UTF_8));
Channel channel = bootstrap.connect(channelAddress).syncUninterruptibly().getChannel();
channel.write(ping).syncUninterruptibly();
flush(channel);
final AtomicReference<Message> pongRef = new AtomicReference<>();
final MessageHandler messageHandler = new MessageHandler() {
@Override
public void onMessage(int msgTypeId, MutableDirectBuffer buffer, int index, int length) {
Message pong = new Message();
pong.typeId = msgTypeId;
pong.payload = buffer.getStringWithoutLengthUtf8(index, length);
pongRef.set(pong);
}
};
while (pingReader.read(messageHandler) == 0) {
sleep(1);
}
Message pong = pongRef.get();
assertNotNull(pong);
UnsafeBuffer srcBuffer = new UnsafeBuffer(pong.payload.getBytes(UTF_8));
pongWriter.write(pong.typeId, srcBuffer, 0, srcBuffer.capacity());
while (pongsReceived.get() == 0) {
sleep(1);
}
channel.close().syncUninterruptibly();
bootstrap.shutdown();
assertEquals(0x01, pong.typeId);
assertEquals("Hello, world", pong.payload);
verify(clientSpy, times(9)).handleUpstream(any(ChannelHandlerContext.class), any(ChannelEvent.class));
verify(clientSpy, times(4)).handleDownstream(any(ChannelHandlerContext.class), any(ChannelEvent.class));
InOrder childConnect = inOrder(clientSpy);
childConnect.verify(clientSpy).channelOpen(any(ChannelHandlerContext.class), any(ChannelStateEvent.class));
childConnect.verify(clientSpy).connectRequested(any(ChannelHandlerContext.class), any(ChannelStateEvent.class));
childConnect.verify(clientSpy).channelBound(any(ChannelHandlerContext.class), any(ChannelStateEvent.class));
childConnect.verify(clientSpy).channelConnected(any(ChannelHandlerContext.class), any(ChannelStateEvent.class));
InOrder childWrite = inOrder(clientSpy);
childWrite.verify(clientSpy).writeRequested(any(ChannelHandlerContext.class), any(MessageEvent.class));
childWrite.verify(clientSpy).flushRequested(any(ChannelHandlerContext.class), any(FlushEvent.class));
childWrite.verify(clientSpy).closeRequested(any(ChannelHandlerContext.class), any(ChannelStateEvent.class));
// asynchronous
verify(clientSpy).writeComplete(any(ChannelHandlerContext.class), any(WriteCompletionEvent.class));
verify(clientSpy).flushed(any(ChannelHandlerContext.class), any(FlushEvent.class));
InOrder childRead = inOrder(clientSpy);
childRead.verify(clientSpy).messageReceived(any(ChannelHandlerContext.class), any(MessageEvent.class));
childRead.verify(clientSpy).channelClosed(any(ChannelHandlerContext.class), any(ChannelStateEvent.class));
InOrder childClose = inOrder(clientSpy);
childClose.verify(clientSpy).channelDisconnected(any(ChannelHandlerContext.class), any(ChannelStateEvent.class));
childClose.verify(clientSpy).channelUnbound(any(ChannelHandlerContext.class), any(ChannelStateEvent.class));
childClose.verify(clientSpy).channelClosed(any(ChannelHandlerContext.class), any(ChannelStateEvent.class));
verifyNoMoreInteractions(clientSpy);
}
private static final class Message {
public int typeId;
public String payload;
}
}