/*
* Copyright 2016 KairosDB Authors
*
* 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.kairosdb.core.telnet;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.*;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.codec.frame.DelimiterBasedFrameDecoder;
import org.jboss.netty.handler.codec.frame.Delimiters;
import org.jboss.netty.handler.codec.string.StringEncoder;
import org.kairosdb.core.KairosDBService;
import org.kairosdb.core.exception.KairosDBException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.concurrent.Executors;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
public class TelnetServer extends SimpleChannelUpstreamHandler implements ChannelPipelineFactory,
KairosDBService
{
private static final Logger logger = LoggerFactory.getLogger(TelnetServer.class);
private final int port;
private final CommandProvider commandProvider;
private final int maxCommandLength;
private InetAddress address;
private ServerBootstrap serverBootstrap;
public TelnetServer(int port,
int maxCommandLength,
CommandProvider commandProvider)
throws UnknownHostException
{
this(null, port, maxCommandLength, commandProvider);
}
@Inject
public TelnetServer(@Named("kairosdb.telnetserver.address") String address,
@Named("kairosdb.telnetserver.port") int port,
@Named("kairosdb.telnetserver.max_command_size") int maxCommandLength,
CommandProvider commandProvider)
throws UnknownHostException
{
checkArgument(maxCommandLength > 0, "command length must be greater than zero");
this.port = port;
this.maxCommandLength = maxCommandLength;
this.commandProvider = checkNotNull(commandProvider);
this.address = InetAddress.getByName(address);
}
@Override
public ChannelPipeline getPipeline() throws Exception
{
ChannelPipeline pipeline = Channels.pipeline();
// Add the text line codec combination first,
DelimiterBasedFrameDecoder frameDecoder = new DelimiterBasedFrameDecoder(
maxCommandLength, Delimiters.lineDelimiter());
pipeline.addLast("framer", frameDecoder);
pipeline.addLast("decoder", new WordSplitter());
pipeline.addLast("encoder", new StringEncoder());
// and then business logic.
pipeline.addLast("handler", this);
return pipeline;
}
private String formatMessage(String[] msg)
{
StringBuilder sb = new StringBuilder();
for (String s : msg)
sb.append(s).append(" ");
return (sb.toString());
}
@Override
public void messageReceived(final ChannelHandlerContext ctx,
final MessageEvent msgevent)
{
final Object message = msgevent.getMessage();
if (message instanceof String[])
{
String[] command = (String[]) message;
String cmd = "";
if (command.length >= 1)
cmd = command[0];
TelnetCommand telnetCommand = commandProvider.getCommand(cmd);
if (telnetCommand != null)
{
try
{
telnetCommand.execute(msgevent.getChannel(), command);
}
catch (Exception e)
{
log("Message: '" + formatMessage(command) + "'", ctx);
log("Failed to execute command: " + formatCommand(command) + " Reason: " + e.getMessage(), ctx, e);
}
}
else
{
log("Message: '" + formatMessage(command) + "'", ctx);
log("Unknown command: '" + cmd + "'", ctx);
}
}
else
{
log("Message: '" + message.toString() + "'", ctx);
log("Invalid message. Must be of type String.", ctx);
}
}
private static void log(String message, ChannelHandlerContext ctx)
{
log(message, ctx, null);
}
private static void log(String message, ChannelHandlerContext ctx, Exception e)
{
message += " From: " + ((InetSocketAddress) ctx.getChannel().getRemoteAddress()).getAddress().getHostAddress();
if (logger.isDebugEnabled())
if (e != null)
logger.debug(message, e);
else
logger.debug(message);
else
{
logger.warn(message);
}
}
@Override
public void start() throws KairosDBException
{
// Configure the server.
serverBootstrap = new ServerBootstrap(
new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("telnet-boss-%d").build()),
Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("telnet-worker-%d").build())));
// Configure the pipeline factory.
serverBootstrap.setPipelineFactory(this);
serverBootstrap.setOption("child.tcpNoDelay", true);
serverBootstrap.setOption("child.keepAlive", true);
serverBootstrap.setOption("reuseAddress", true);
// Bind and start to accept incoming connections.
serverBootstrap.bind(new InetSocketAddress(address, port));
}
public InetAddress getAddress()
{
return address;
}
@Override
public void stop()
{
if (serverBootstrap != null)
serverBootstrap.shutdown();
}
private static String formatCommand(String[] command)
{
StringBuilder builder = new StringBuilder();
for (String s : command)
{
builder.append(s).append(" ");
}
return builder.toString();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception
{
logger.error("Error in TelnetServer", e.getCause());
}
}