/**
* 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.fireChannelBound;
import static org.jboss.netty.channel.Channels.fireChannelClosed;
import static org.jboss.netty.channel.Channels.fireChannelUnbound;
import java.net.URI;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelException;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ChildChannelStateEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.ChannelGroupFuture;
import org.jboss.netty.channel.group.ChannelGroupFutureListener;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.kaazing.k3po.driver.internal.netty.bootstrap.BootstrapFactory;
import org.kaazing.k3po.driver.internal.netty.bootstrap.ServerBootstrap;
import org.kaazing.k3po.driver.internal.netty.bootstrap.channel.AbstractServerChannelSink;
import org.kaazing.k3po.driver.internal.netty.channel.ChannelAddress;
import org.kaazing.k3po.driver.internal.netty.channel.ChannelAddressFactory;
public class BBoshServerChannelSink extends AbstractServerChannelSink<BBoshServerChannel> {
private final BBoshHandshakeChildChannelPipelineFactory pipelineFactory;
private final ConcurrentNavigableMap<URI, BBoshServerChannel> bboshBindings;
public BBoshServerChannelSink() {
this(new ConcurrentSkipListMap<URI, BBoshServerChannel>());
}
private BBoshServerChannelSink(ConcurrentNavigableMap<URI, BBoshServerChannel> bboshBindings) {
this.pipelineFactory = new BBoshHandshakeChildChannelPipelineFactory(bboshBindings);
this.bboshBindings = bboshBindings;
}
public void setAddressFactory(ChannelAddressFactory addressFactory) {
pipelineFactory.setAddressFactory(addressFactory);
}
@Override
public void setBootstrapFactory(BootstrapFactory bootstrapFactory) {
super.setBootstrapFactory(bootstrapFactory);
pipelineFactory.setBootstrapFactory(bootstrapFactory);
}
@Override
protected void bindRequested(ChannelPipeline pipeline, ChannelStateEvent evt) throws Exception {
final BBoshServerChannel bboshBindChannel = (BBoshServerChannel) evt.getChannel();
final ChannelFuture bboshBindFuture = evt.getFuture();
final ChannelAddress bboshLocalAddress = (ChannelAddress) evt.getValue();
URI bboshLocation = bboshLocalAddress.getLocation();
BBoshServerChannel bboshBoundChannel = bboshBindings.putIfAbsent(bboshLocation, bboshBindChannel);
if (bboshBoundChannel != null) {
bboshBindFuture.setFailure(new ChannelException(format("Duplicate bind failed: %s", bboshLocation)));
}
ChannelAddress address = bboshLocalAddress.getTransport();
String schemeName = address.getLocation().getScheme();
String bboshSchemeName = bboshLocalAddress.getLocation().getScheme();
ServerBootstrap bootstrap = bootstrapFactory.newServerBootstrap(schemeName);
bootstrap.setParentHandler(createParentHandler(bboshBindChannel));
bootstrap.setPipelineFactory(pipelineFactory);
bootstrap.setOption(format("%s.nextProtocol", schemeName), bboshSchemeName);
// bind transport
ChannelFuture bindFuture = bootstrap.bindAsync(address);
if (bindFuture.isDone()) {
handleBBoshTransportBindComplete(bboshBindChannel, bboshBindFuture, bboshLocalAddress, bindFuture);
}
else {
bindFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture bindFuture) throws Exception {
handleBBoshTransportBindComplete(bboshBindChannel, bboshBindFuture, bboshLocalAddress, bindFuture);
}
});
}
}
@Override
protected void unbindRequested(ChannelPipeline pipeline, ChannelStateEvent evt) throws Exception {
final BBoshServerChannel bboshUnbindChannel = (BBoshServerChannel) evt.getChannel();
final ChannelFuture bboshUnbindFuture = evt.getFuture();
ChannelAddress bboshLocalAddress = bboshUnbindChannel.getLocalAddress();
URI bboshLocation = bboshLocalAddress.getLocation();
if (!bboshBindings.remove(bboshLocation, bboshUnbindChannel)) {
bboshUnbindFuture.setFailure(new ChannelException("Channel not bound").fillInStackTrace());
return;
}
Channel transport = bboshUnbindChannel.getTransport();
ChannelFuture unbindFuture = transport.unbind();
if (unbindFuture.isDone()) {
handleBBoshTransportUnbindComplete(bboshUnbindChannel, bboshUnbindFuture, unbindFuture);
}
else {
unbindFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture unbindFuture) throws Exception {
handleBBoshTransportUnbindComplete(bboshUnbindChannel, bboshUnbindFuture, unbindFuture);
}
});
}
}
@Override
protected void closeRequested(ChannelPipeline pipeline, ChannelStateEvent evt) throws Exception {
final BBoshServerChannel bboshCloseChannel = (BBoshServerChannel) evt.getChannel();
final ChannelFuture bboshCloseFuture = evt.getFuture();
boolean wasBound = bboshCloseChannel.isBound();
if (bboshCloseChannel.setClosed()) {
if (wasBound) {
unbindRequested(pipeline, evt);
}
Channel transport = bboshCloseChannel.getTransport();
if (transport != null) {
ChannelFuture closeFuture = transport.close();
if (closeFuture.isDone()) {
handleBBoshTransportCloseComplete(bboshCloseChannel, bboshCloseFuture, closeFuture);
}
else {
closeFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture closeFuture) throws Exception {
handleBBoshTransportCloseComplete(bboshCloseChannel, bboshCloseFuture, closeFuture);
}
});
}
}
}
}
private ChannelHandler createParentHandler(BBoshServerChannel channel) {
return new SimpleChannelHandler() {
private final ChannelGroup childChannels = new DefaultChannelGroup();
@Override
public void childChannelOpen(ChannelHandlerContext ctx, ChildChannelStateEvent e) throws Exception {
childChannels.add(e.getChildChannel());
super.childChannelOpen(ctx, e);
}
@Override
public void childChannelClosed(ChannelHandlerContext ctx, ChildChannelStateEvent e) throws Exception {
childChannels.remove(e.getChildChannel());
super.childChannelClosed(ctx, e);
}
@Override
public void closeRequested(final ChannelHandlerContext ctx, final ChannelStateEvent e) throws Exception {
// close up any transports previously in use for HTTP
childChannels.close().addListener(new ChannelGroupFutureListener() {
@Override
public void operationComplete(ChannelGroupFuture future) throws Exception {
ctx.sendDownstream(e);
}
});
}
};
}
private static void handleBBoshTransportBindComplete(
final BBoshServerChannel bboshBindChannel,
final ChannelFuture bboshBindFuture,
final ChannelAddress bboshLocalAddress, ChannelFuture future) {
if (future.isSuccess()) {
bboshBindChannel.setTransport(future.getChannel());
bboshBindChannel.setLocalAddress(bboshLocalAddress);
bboshBindChannel.setBound();
fireChannelBound(bboshBindChannel, bboshBindChannel.getLocalAddress());
bboshBindFuture.setSuccess();
}
else {
bboshBindFuture.setFailure(future.getCause());
}
}
private static void handleBBoshTransportUnbindComplete(
final BBoshServerChannel bboshUnbindChannel,
final ChannelFuture bboshUnbindFuture, ChannelFuture future) {
if (future.isSuccess()) {
fireChannelUnbound(bboshUnbindChannel);
bboshUnbindFuture.setSuccess();
}
else {
bboshUnbindFuture.setFailure(future.getCause());
}
}
private static void handleBBoshTransportCloseComplete(
BBoshServerChannel bboshCloseChannel,
ChannelFuture bboshCloseFuture,
ChannelFuture closeFuture) {
if (closeFuture.isSuccess()) {
fireChannelClosed(bboshCloseChannel);
bboshCloseFuture.setSuccess();
}
else {
bboshCloseFuture.setFailure(closeFuture.getCause());
}
}
}