/**
* 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.deephacks.westty.internal.protobuf;
import com.google.protobuf.MessageLite;
import org.deephacks.westty.config.ProtobufConfig;
import org.deephacks.westty.config.ServerSpecificConfigProxy;
import org.deephacks.westty.protobuf.FailureMessages.Failure;
import org.deephacks.westty.protobuf.ProtobufClient;
import org.deephacks.westty.protobuf.ProtobufSerializer;
import org.deephacks.westty.spi.ProviderShutdownEvent;
import org.deephacks.westty.spi.ProviderStartupEvent;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.codec.frame.LengthFieldBasedFrameDecoder;
import org.jboss.netty.handler.codec.frame.LengthFieldPrepender;
import org.jboss.netty.handler.codec.oneone.OneToOneDecoder;
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
import org.jboss.netty.handler.execution.ExecutionHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import static org.jboss.netty.buffer.ChannelBuffers.wrappedBuffer;
@Singleton
class ProtobufPipelineFactory implements ChannelPipelineFactory {
private static final Logger log = LoggerFactory.getLogger(ProtobufChannelHandler.class);
@Inject
private ProtobufChannelHandler channelHandler;
private ProtobufSerializer serializer;
private ThreadPoolExecutor executor;
private ExecutionHandler executionHandler;
private Channel channel;
private ServerBootstrap bootstrap;
private ProtobufConfig config;
@Inject
public ProtobufPipelineFactory(ServerSpecificConfigProxy<ProtobufConfig> config, ThreadPoolExecutor executor, ProtobufSerializer serializer) {
this.config = config.get();
this.executor = executor;
this.serializer = serializer;
ExecutorService workers = Executors.newCachedThreadPool();
ExecutorService boss = Executors.newCachedThreadPool();
bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(boss,
workers, this.config.getIoWorkerCount()));
bootstrap.setPipelineFactory(this);
bootstrap.setOption("child.tcpNoDelay", true);
bootstrap.setOption("child.keepAlive", true);
}
public void startup(@Observes ProviderStartupEvent event) {
channel = bootstrap.bind(new InetSocketAddress(config.getPort()));
log.info("Protobuf listening on port {}.", config.getPort());
}
public void shutdown(@Observes ProviderShutdownEvent event) {
log.info("Closing channels.");
channelHandler.getClientChannels().close();
if (channel != null) {
channel.close().awaitUninterruptibly(5000);
}
if (bootstrap != null) {
bootstrap.releaseExternalResources();
}
log.info("All channels closed.");
}
@Override
public ChannelPipeline getPipeline() throws Exception {
if (executionHandler == null) {
this.executionHandler = new ExecutionHandler(executor);
}
return Channels.pipeline(new LengthFieldBasedFrameDecoder(
ProtobufClient.MESSAGE_MAX_SIZE_10MB, 0, ProtobufClient.MESSAGE_LENGTH,
0, ProtobufClient.MESSAGE_LENGTH),
new WesttyProtobufDecoder(serializer), new LengthFieldPrepender(
ProtobufClient.MESSAGE_LENGTH),
new WesttyProtobufEncoder(serializer), executionHandler, channelHandler);
}
private static class WesttyProtobufDecoder extends OneToOneDecoder {
private final ProtobufSerializer serializer;
public WesttyProtobufDecoder(ProtobufSerializer serializer) {
this.serializer = serializer;
}
@Override
protected Object decode(ChannelHandlerContext ctx, Channel channel, Object msg)
throws Exception {
if (!(msg instanceof ChannelBuffer)) {
return msg;
}
ChannelBuffer buf = (ChannelBuffer) msg;
Object decoded = serializer.read(buf.array());
if (decoded instanceof Failure) {
ctx.getChannel().write(decoded);
return null;
} else {
return decoded;
}
}
}
private static class WesttyProtobufEncoder extends OneToOneEncoder {
private ProtobufSerializer serializer;
public WesttyProtobufEncoder(ProtobufSerializer serializer) {
this.serializer = serializer;
}
@Override
protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg)
throws Exception {
if (msg instanceof MessageLite) {
return wrappedBuffer(serializer.write(msg));
}
if (msg instanceof MessageLite.Builder) {
return wrappedBuffer(((MessageLite.Builder) msg).build().toByteArray());
}
return msg;
}
}
}