/**
* 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;
import static java.util.concurrent.Executors.newCachedThreadPool;
import static org.jboss.netty.channel.Channels.pipeline;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeoutException;
import org.jboss.netty.channel.Channel;
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.ChannelPipelineFactory;
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.DefaultChannelGroup;
import org.jboss.netty.channel.socket.nio.NioClientBossPool;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.channel.socket.nio.NioServerBossPool;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.channel.socket.nio.NioWorker;
import org.jboss.netty.channel.socket.nio.NioWorkerPool;
import org.jboss.netty.channel.socket.nio.ShareableWorkerPool;
import org.jboss.netty.handler.logging.LoggingHandler;
import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
import org.kaazing.k3po.driver.internal.control.handler.ControlDecoder;
import org.kaazing.k3po.driver.internal.control.handler.ControlEncoder;
import org.kaazing.k3po.driver.internal.control.handler.ControlServerHandler;
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.channel.ChannelAddress;
import org.kaazing.k3po.driver.internal.netty.channel.ChannelAddressFactory;
public class RobotServer {
private final ChannelGroup channelGroup;
private final List<ControlServerHandler> controlHandlers;
private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(RobotServer.class);
private BootstrapFactory bootstrapFactory;
private final URI controlURI;
private Channel serverChannel;
private final boolean verbose;
private final ClassLoader scriptLoader;
private ShareableWorkerPool<NioWorker> sharedWorkerPool;
private NioClientSocketChannelFactory clientChannelFactory;
private NioServerSocketChannelFactory serverChannelFactory;
public RobotServer(URI controlURI, boolean verbose, ClassLoader scriptLoader) {
this.controlURI = controlURI;
this.verbose = verbose;
this.scriptLoader = scriptLoader;
this.channelGroup = new DefaultChannelGroup("robot-server");
this.controlHandlers = new CopyOnWriteArrayList<>();
}
public void start() throws Exception {
if (controlURI == null) {
throw new NullPointerException("controlURI");
}
Map<String, Object> options = new HashMap<>();
// TODO: options.put("tcp.transport", "socks://...");
final ChannelAddressFactory addressFactory = ChannelAddressFactory.newChannelAddressFactory();
ChannelAddress localAddress = addressFactory.newChannelAddress(controlURI, options);
NioClientBossPool clientBossPool = new NioClientBossPool(newCachedThreadPool(), 1);
NioServerBossPool serverBossPool = new NioServerBossPool(newCachedThreadPool(), 1);
NioWorkerPool workerPool = new NioWorkerPool(newCachedThreadPool(), 1);
sharedWorkerPool = new ShareableWorkerPool<>(workerPool);
clientChannelFactory = new NioClientSocketChannelFactory(clientBossPool, sharedWorkerPool);
serverChannelFactory = new NioServerSocketChannelFactory(serverBossPool, sharedWorkerPool);
Map<Class<?>, Object> injectables = new HashMap<>();
injectables.put(ChannelAddressFactory.class, addressFactory);
injectables.put(NioClientSocketChannelFactory.class, clientChannelFactory);
injectables.put(NioServerSocketChannelFactory.class, serverChannelFactory);
bootstrapFactory = BootstrapFactory.newBootstrapFactory(injectables);
String transportName = controlURI.getScheme();
ServerBootstrap server = bootstrapFactory.newServerBootstrap(transportName);
server.setPipelineFactory(new ChannelPipelineFactory() {
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = pipeline();
ChannelHandler decoder = new ControlDecoder();
pipeline.addLast("control.decoder", decoder);
ChannelHandler encoder = new ControlEncoder();
pipeline.addLast("control.encoder", encoder);
if (verbose) {
ChannelHandler logging = new LoggingHandler("robot.server", false);
pipeline.addLast("control.logging", logging);
}
ControlServerHandler controller = new ControlServerHandler();
controller.setScriptLoader(scriptLoader);
pipeline.addLast("control.handler", controller);
return pipeline;
}
});
/* Keep track of all open channels */
server.setParentHandler(new SimpleChannelHandler() {
@Override
public void childChannelOpen(ChannelHandlerContext ctx, ChildChannelStateEvent e) throws Exception {
LOGGER.debug("Control Channel Opened");
Channel childChannel = e.getChildChannel();
channelGroup.add(childChannel);
final ControlServerHandler controller =
(ControlServerHandler) childChannel.getPipeline().getContext("control.handler").getHandler();
// Add the controller to our list
controlHandlers.add(controller);
// And remove it when the channel is closed.
controller.getChannelClosedFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
controlHandlers.remove(controller);
}
});
}
});
serverChannel = server.bind(localAddress);
}
public void stop() throws TimeoutException {
boolean isDebugEnabled = LOGGER.isDebugEnabled();
if (serverChannel != null) {
serverChannel.close().awaitUninterruptibly(2000);
if (isDebugEnabled) {
LOGGER.debug("Server control channel closed.");
}
}
channelGroup.close().awaitUninterruptibly(2000);
if (isDebugEnabled) {
LOGGER.debug("Control channels closed.");
}
// Note it is important that we wait for the control handler to process the channelClosed
// event, otherwise there will be a race between it and releasing resources.
for (ControlServerHandler controller : controlHandlers) {
controller.getChannelClosedFuture().awaitUninterruptibly(2000);
// controller.completeShutDown(2000);
}
if (clientChannelFactory != null) {
LOGGER.debug("Releasing tcp client channel factory");
clientChannelFactory.shutdown();
clientChannelFactory.releaseExternalResources();
LOGGER.debug("Released tcp client channel factory");
}
if (serverChannelFactory != null) {
LOGGER.debug("Releasing tcp server channel factory");
serverChannelFactory.shutdown();
serverChannelFactory.releaseExternalResources();
LOGGER.debug("Released tcp server channel factory");
}
if (sharedWorkerPool != null) {
LOGGER.debug("Destroying shared worker pool");
sharedWorkerPool.destroy();
LOGGER.debug("Destroyed shared worker pool.");
}
if (bootstrapFactory != null) {
LOGGER.debug("Releasing external resources");
bootstrapFactory.releaseExternalResources();
LOGGER.debug("External resources released.");
}
}
public void join() throws InterruptedException {
if (serverChannel != null) {
serverChannel.getCloseFuture().await();
}
}
}