/**
* 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.bbosh;
import static java.lang.String.format;
import static org.jboss.netty.channel.Channels.future;
import static org.kaazing.k3po.driver.internal.channel.Channels.chainWriteCompletes;
import java.util.Deque;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.ChannelException;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.kaazing.k3po.driver.internal.netty.bootstrap.bbosh.BBoshHttpHeaders.Names;
import org.kaazing.k3po.driver.internal.netty.bootstrap.bbosh.BBoshHttpHeaders.Values;
import org.kaazing.k3po.driver.internal.netty.bootstrap.channel.AbstractChannelSink;
import org.kaazing.k3po.driver.internal.netty.bootstrap.http.HttpChannelConfig;
import org.kaazing.k3po.driver.internal.netty.bootstrap.http.HttpChildChannel;
public class BBoshPollingChildChannelSink extends AbstractChannelSink {
private final Deque<MessageEvent> messages;
private AtomicInteger nextSequenceNo;
private AtomicReference<HttpChildChannel> httpChannelRef;
private final Runnable flushTask = new Runnable() {
@Override
public void run() {
HttpChildChannel httpChannel = httpChannelRef.get();
if (!messages.isEmpty()) {
while (!messages.isEmpty()) {
MessageEvent head = messages.removeFirst();
ChannelBuffer message = (ChannelBuffer) head.getMessage();
int readableBytes = message.readableBytes();
ChannelFuture future = head.getFuture();
ChannelFuture httpFuture = httpChannel.write(message);
chainWriteCompletes(httpFuture, future, readableBytes);
}
httpChannelRef.compareAndSet(httpChannel, null);
httpChannel.close();
}
}
};
public BBoshPollingChildChannelSink(int nextSequenceNo) {
this.nextSequenceNo = new AtomicInteger(nextSequenceNo);
this.httpChannelRef = new AtomicReference<>();
this.messages = new ConcurrentLinkedDeque<>();
}
@Override
protected void writeRequested(ChannelPipeline pipeline, MessageEvent e) throws Exception {
messages.addLast(e);
HttpChildChannel httpChannel = httpChannelRef.get();
if (httpChannel != null) {
ChannelPipeline httpPipeline = httpChannel.getPipeline();
httpPipeline.execute(flushTask);
}
}
public ChannelFuture attach(int sequenceNo, HttpChildChannel httpChannel) {
ChannelFuture httpFuture = future(httpChannel);
attach(sequenceNo, httpChannel, httpFuture);
return httpFuture;
}
public void detach(HttpChildChannel httpChannel) {
if (httpChannel.isOpen()) {
assert httpChannel == httpChannelRef.get();
httpChannelRef.set(null);
httpChannel.close();
}
}
private void attach(final int sequenceNo, final HttpChildChannel httpChannel, final ChannelFuture httpFuture) {
// TODO: handle out of order sequence arrival more defensively
if (sequenceNo < nextSequenceNo.get()) {
String message = format("Replayed sequence number: %d", sequenceNo);
ChannelException exception = new ChannelException(message);
exception.fillInStackTrace();
httpFuture.setFailure(exception);
}
else if (nextSequenceNo.compareAndSet(sequenceNo, sequenceNo + 1)) {
HttpChannelConfig httpConfig = httpChannel.getConfig();
HttpHeaders httpHeaders = httpConfig.getWriteHeaders();
httpHeaders.set(Names.CACHE_CONTROL, Values.NO_CACHE);
httpHeaders.set(Names.CONTENT_TYPE, Values.APPLICATION_OCTET_STREAM);
httpConfig.setMaximumBufferedContentLength(8192);
httpChannelRef.set(httpChannel);
flushTask.run();
httpFuture.setSuccess();
}
else {
Runnable reorderTask = new Runnable() {
@Override
public void run() {
attach(sequenceNo, httpChannel, httpFuture);
}
};
ChannelPipeline httpPipeline = httpChannel.getPipeline();
httpPipeline.execute(reorderTask);
}
}
}