package com.sissi.server.netty.impl; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.timeout.IdleStateEvent; import io.netty.util.AttributeKey; import io.netty.util.ReferenceCountUtil; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.nio.charset.Charset; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.sissi.addressing.Addressing; import com.sissi.commons.Trace; import com.sissi.commons.apache.IOUtil; import com.sissi.context.JIDContext; import com.sissi.context.JIDContextBuilder; import com.sissi.context.JIDContextParam; import com.sissi.feed.FeederBuilder; import com.sissi.io.read.Reader; import com.sissi.looper.Looper; import com.sissi.looper.LooperBuilder; import com.sissi.pipeline.InputFinder; import com.sissi.pipeline.Output; import com.sissi.pipeline.OutputBuilder; import com.sissi.resource.ResourceCounter; import com.sissi.server.getout.Getout; import com.sissi.server.netty.ChannelHandlerBuilder; import com.sissi.server.tls.SSLContextBuilder; import com.sissi.server.tls.impl.FixDomainStartTls; /** * @author kim 2013年12月1日 */ public class MainServerHandlerBuilder implements ChannelHandlerBuilder { private final AttributeKey<JIDContext> attrContext = AttributeKey.valueOf("ATTR_CONTEXT"); private final AttributeKey<Looper> attrConnector = AttributeKey.valueOf("CONNECTOR_ATTR"); private final String resource = PersonalServerHandler.class.getSimpleName(); private final Log log = LogFactory.getLog(this.getClass()); private final Reader reader; private final Getout getout; private final InputFinder finder; private final Addressing addressing; private final FeederBuilder feederBuilder; private final LooperBuilder looperBuilder; private final OutputBuilder outputBuilder; private final ResourceCounter resourceCounter; private final SSLContextBuilder sslContextBuilder; private final JIDContextBuilder jidContextBuilder; public MainServerHandlerBuilder(Reader reader, Getout getout, InputFinder finder, Addressing addressing, FeederBuilder feederBuilder, LooperBuilder looperBuilder, OutputBuilder outputBuilder, ResourceCounter resourceCounter, SSLContextBuilder sslContextBuilder, JIDContextBuilder jidContextBuilder) { super(); this.reader = reader; this.finder = finder; this.getout = getout; this.addressing = addressing; this.feederBuilder = feederBuilder; this.looperBuilder = looperBuilder; this.outputBuilder = outputBuilder; this.resourceCounter = resourceCounter; this.sslContextBuilder = sslContextBuilder; this.jidContextBuilder = jidContextBuilder; } public ChannelHandler build() throws IOException { return new PersonalServerHandler(); } private class PersonalServerHandler extends ChannelInboundHandlerAdapter { private final PipedInputStream inPipe = new PipedInputStream(); private final PipedOutputStream outPipe = new PipedOutputStream(inPipe); private final BufferedInputStream input = new BufferedInputStream(inPipe); private final BufferedOutputStream output = new BufferedOutputStream(outPipe); public PersonalServerHandler() throws IOException { super(); } public void channelRegistered(final ChannelHandlerContext ctx) throws IOException { this.createContext(ctx).createLooper(ctx); MainServerHandlerBuilder.this.resourceCounter.increment(MainServerHandlerBuilder.this.resource); } public void channelUnregistered(ChannelHandlerContext ctx) throws IOException { this.closeContext(ctx).closeLooper(ctx); MainServerHandlerBuilder.this.resourceCounter.decrement(MainServerHandlerBuilder.this.resource); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws IOException { try { this.copyToBytes(this.output, this.logIfNecessary(ctx, ByteBuf.class.cast(msg))).flush(); } finally { ReferenceCountUtil.release(msg); } } public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt.getClass() == IdleStateEvent.class) { ctx.attr(MainServerHandlerBuilder.this.attrContext).get().ping(); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { MainServerHandlerBuilder.this.log.error(cause.toString()); Trace.trace(MainServerHandlerBuilder.this.log, cause); ctx.close(); } private PersonalServerHandler closeLooper(ChannelHandlerContext ctx) { ctx.attr(MainServerHandlerBuilder.this.attrConnector).get().stop(); return this; } private PersonalServerHandler closeContext(ChannelHandlerContext ctx) throws IOException { JIDContext context = ctx.attr(MainServerHandlerBuilder.this.attrContext).get(); MainServerHandlerBuilder.this.getout.getout(context); MainServerHandlerBuilder.this.addressing.leave(context); this.closeParser(); return this; } private PersonalServerHandler closeParser() throws IOException { this.output.write(-1); this.output.flush(); IOUtil.closeQuietly(this.output); IOUtil.closeQuietly(this.outPipe); return this; } private PersonalServerHandler createLooper(final ChannelHandlerContext ctx) throws IOException { Looper looper = MainServerHandlerBuilder.this.looperBuilder.build(MainServerHandlerBuilder.this.reader.future(this.input), MainServerHandlerBuilder.this.feederBuilder.build(ctx.attr(MainServerHandlerBuilder.this.attrContext).get(), MainServerHandlerBuilder.this.finder)); ctx.attr(MainServerHandlerBuilder.this.attrConnector).set(looper); looper.start(); return this; } private PersonalServerHandler createContext(final ChannelHandlerContext ctx) { ctx.attr(MainServerHandlerBuilder.this.attrContext).set(MainServerHandlerBuilder.this.jidContextBuilder.build(null, new NettyProxyContextParam(ctx))); return this; } private OutputStream copyToBytes(OutputStream output, ByteBuf byteBuf) throws IOException { byteBuf.readBytes(output, byteBuf.readableBytes()); return output; } private ByteBuf logIfNecessary(ChannelHandlerContext ctx, ByteBuf byteBuf) { if (MainServerHandlerBuilder.this.log.isInfoEnabled()) { JIDContext context = ctx.attr(MainServerHandlerBuilder.this.attrContext).get(); MainServerHandlerBuilder.this.log.info("Read on " + (context != null && context.jid() != null ? context.jid().asString() : "N/A") + ": " + byteBuf.toString(Charset.forName("UTF-8"))); byteBuf.readerIndex(0); } return byteBuf; } } private class NettyProxyContextParam implements JIDContextParam { private final FixDomainStartTls startTls; private final ChannelHandlerContext ctx; private final Output output; public NettyProxyContextParam(ChannelHandlerContext ctx) { super(); this.ctx = ctx; this.startTls = new FixDomainStartTls(MainServerHandlerBuilder.this.sslContextBuilder, ctx); this.output = MainServerHandlerBuilder.this.outputBuilder.build(new NetworkTransfer(this.startTls, ctx)); } @Override public <T> T find(String key, Class<T> clazz) { switch (key) { case JIDContextParam.KEY_OUTPUT: return clazz.cast(this.output); case JIDContextParam.KEY_STARTTLS: return clazz.cast(this.startTls); case JIDContextParam.KEY_ADDRESS: return clazz.cast(this.ctx.channel().remoteAddress()); } return null; } } }