/*
* Copyright (c) 2015-2016, Christoph Engelbert (aka noctarius) and
* contributors. 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 com.noctarius.tengi.server.impl.transport.http;
import com.noctarius.tengi.core.connection.Connection;
import com.noctarius.tengi.core.connection.Transport;
import com.noctarius.tengi.core.impl.ExceptionUtil;
import com.noctarius.tengi.core.impl.FutureUtil;
import com.noctarius.tengi.core.model.Identifier;
import com.noctarius.tengi.core.model.Message;
import com.noctarius.tengi.spi.buffer.MemoryBuffer;
import com.noctarius.tengi.spi.buffer.impl.MemoryBufferFactory;
import com.noctarius.tengi.spi.connection.ConnectionContext;
import com.noctarius.tengi.spi.connection.packets.PollingRequest;
import com.noctarius.tengi.spi.connection.packets.PollingResponse;
import com.noctarius.tengi.spi.serialization.Serializer;
import com.noctarius.tengi.spi.serialization.codec.AutoClosableEncoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class HttpConnectionContext
extends ConnectionContext<Channel> {
private final Queue<QueueEntry> messageQueue = new ConcurrentLinkedQueue<>();
private final Lock longPollingLock = new ReentrantLock();
private final Condition longPollingWaitCondition = longPollingLock.newCondition();
HttpConnectionContext(Identifier connectionId, Serializer serializer, Transport transport) {
super(connectionId, serializer, transport);
}
@Override
public CompletableFuture<Message> writeMemoryBuffer(MemoryBuffer memoryBuffer, Message message) {
CompletableFuture<Message> future = new CompletableFuture<>();
messageQueue.add(new QueueEntry(memoryBuffer, message, future));
longPollingLock.lock();
try {
longPollingWaitCondition.signal();
} finally {
longPollingLock.unlock();
}
return future;
}
@Override
public CompletableFuture<Connection> writeSocket(Channel channel, Connection connection, MemoryBuffer memoryBuffer)
throws Exception {
ByteBuf bb = channel.alloc().directBuffer();
MemoryBuffer buffer = preparePacket(MemoryBufferFactory.create(bb));
buffer.writeBuffer(memoryBuffer);
return FutureUtil.executeAsync(() -> {
sendHttpResponse(channel, bb);
return connection;
});
}
@Override
public CompletableFuture<Connection> close(Connection connection) {
return CompletableFuture.completedFuture(connection);
}
@Override
public void processPollingRequest(Channel channel, Connection connection, PollingRequest request) {
try {
if (!getConnectionId().equals(connection.getConnectionId())) {
channel.close().sync();
return;
}
Collection<QueueEntry> messages = drainMessageQueue();
if (messages.size() == 0) {
longPollingLock.lock();
try {
longPollingWaitCondition.await(5, TimeUnit.SECONDS);
messages = drainMessageQueue();
} catch (InterruptedException e) {
// ignore and just return an empty response
} finally {
longPollingLock.unlock();
}
}
PollingResponse pollingResponse = new PollingResponse(new QueueEntryMessageList(messages));
ByteBuf buffer = channel.alloc().directBuffer();
MemoryBuffer memoryBuffer = preparePacket(MemoryBufferFactory.create(buffer));
try (AutoClosableEncoder encoder = getSerializer().retrieveEncoder(memoryBuffer)) {
encoder.writeObject("pollingResponse", Message.create(pollingResponse));
}
sendHttpResponse(channel, buffer);
} catch (Exception e) {
throw ExceptionUtil.rethrow(e);
}
}
private void sendHttpResponse(Channel channel, ByteBuf buffer) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buffer);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, getProtocol().getMimeType());
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, buffer.writerIndex());
response.headers().set(HttpHeaderNames.CONNECTION, "close");
ChannelFuture channelFuture = channel.writeAndFlush(response);
channelFuture.addListener(ChannelFutureListener.CLOSE);
}
private Collection<QueueEntry> drainMessageQueue() {
Collection<QueueEntry> queueEntries = new ArrayList<>();
QueueEntry queueEntry;
while ((queueEntry = messageQueue.poll()) != null) {
queueEntries.add(queueEntry);
}
return queueEntries;
}
private static final class QueueEntry {
final MemoryBuffer memoryBuffer;
final Message message;
final CompletableFuture<Message> future;
private QueueEntry(MemoryBuffer memoryBuffer, Message message, CompletableFuture<Message> future) {
this.memoryBuffer = memoryBuffer;
this.message = message;
this.future = future;
}
}
private static final class QueueEntryMessageList
extends AbstractCollection<Message>
implements List<Message> {
private final Collection<QueueEntry> messages;
private QueueEntryMessageList(Collection<QueueEntry> messages) {
this.messages = messages;
}
@Override
public Iterator<Message> iterator() {
final Iterator<QueueEntry> iterator = messages.iterator();
return new Iterator<Message>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public Message next() {
return iterator.next().message;
}
};
}
@Override
public boolean addAll(int index, Collection<? extends Message> c) {
throw new UnsupportedOperationException();
}
@Override
public Message get(int index) {
throw new UnsupportedOperationException();
}
@Override
public Message set(int index, Message element) {
throw new UnsupportedOperationException();
}
@Override
public void add(int index, Message element) {
throw new UnsupportedOperationException();
}
@Override
public Message remove(int index) {
throw new UnsupportedOperationException();
}
@Override
public int indexOf(Object o) {
throw new UnsupportedOperationException();
}
@Override
public int lastIndexOf(Object o) {
throw new UnsupportedOperationException();
}
@Override
public ListIterator<Message> listIterator() {
throw new UnsupportedOperationException();
}
@Override
public ListIterator<Message> listIterator(int index) {
throw new UnsupportedOperationException();
}
@Override
public List<Message> subList(int fromIndex, int toIndex) {
throw new UnsupportedOperationException();
}
@Override
public int size() {
return messages.size();
}
}
}