Guide 1: Step-by-Step for Telnet Service Core Example (1)

Overview

The Telnet Service is a library that provides easy-to-integrate TELNET functionality to Java(TM) applications. Although its main purpose is to integrate servers as an administration console, its versatility puts creativity as the only limitation to its usage.

Introduction

In this example, an application developer wishes to integrate TELNET functionality to an existing application. They will implement the interfaces that are required for dictating the behavior of the TelnetService , and pass those to the service before starting it. The service will then start interacting with those provided objects feeding information and expecting response that dictates its behavior.

The following sections show the basics of this integration activity using a simple (trivial) example.

Related Documentation

Steps To Integrate Telnet Service To Your Application

Step 0: Plan the features to be implemented

The service abstracts the details of of a telnet connection, and to do so was designed as an interactive wrapper around a set of components that take care of the connectivity details. To best use the service, it is important to be aware of its functionality and main components.

It goes without saying that a proper planning and design of the expected behavior are always the way to go.

In this example, we will write a simple interface with 3 commands: sayhello, close, and shutdown.

  • sayhello responds with "Hello, text !", text being the parameter passed
  • close will close the current session
  • shutdown will shutdown the server.

Step 1: Build a mini-container and integrate bare service

Setup the simple container, which will initialize and bind the service (the full source is available for download towards the end of this document). [Example1.java ]

public class Example1
{

The service will keep on running while its ServiceState 's isRunning flag is true .

    public static class ServiceState
    {
        boolean isRunning = false;
    }

    public static void main(String[] args) throws TelnetServiceStartException {
        Example1 example = new Example1();
        example.initialize();
        example.run();
    }

    protected ServiceState serviceState;

A TelnetService instance holds the service, in our example, it is instantiated in a main() method, but in real life it would be dropped as an osgi service, for example, or an ear.

Initializing the service is basically giving it a bind port (the default one is 23). Then we start it and keep it running while it's not requested to stop.

    protected TelnetService telnetService;
    
    public void initialize() {
        this.serviceState = new ServiceState();

        this.telnetService = new TelnetService();
        this.telnetService.setBindPort(2323);
    }
    
    public void run() throws TelnetServiceStartException {
        Logger logger = Logger.getLogger(Example1.class);

        this.serviceState.isRunning = true;
        this.telnetService.start();

        while (this.serviceState.isRunning) {
            try {
                Thread.sleep(1331);
            }
            catch (InterruptedException e) {
                logger.debug("Interrupted", e);
            }
        }
    }
}

Step 2: Let's make sure everything compiles and the bare service works

To try this example, compile it (it's maven enabled) and run it, then start your favorite telnet client connecting to localhost 2323. If you can connect, you're good. The service does nothing :-) or almost ;-)

Let's make it do something.

Step 3: Implementing a TelnetServiceExtension

You will probably choose to use the available extensions that provide the commonly needed functionality. However, if those are not suitable, then writing an extension provides the most control over the look-and-feal and the logic of the console.

A TelnetServiceExtension extends the service's functionality by providing ActionListener s and PromptDecorators , as well as other possible service feature refinements as they get implemented.

The service state will be passed on from the main setup, and will be used to implement the shutdown command.

There are a few identification methods (name, description). [TelnetServiceExtensionExample1Impl.java ]

public class TelnetServiceExtensionExample1Impl
    extends TelnetServiceExtensionAbstractImpl
    implements TelnetServiceExtension
{
    ServiceState serviceState;
    
    public TelnetServiceExtensionExample1Impl(ServiceState serviceState) {
        this.serviceState = serviceState;
    }
    
    public String getName() {
        return "Example1-Extension";
    }

    public String getDescription() {
        return "Extends the telnet service with basic demonstration functionality (Example 1)";
    }

This extension signals that it has an action listener to provide, and a way to create instances of it.

    public boolean providesActionListener() {
        return true;
    }

    public ActionListener createActionListener()
        throws ActionListenerCreateException {
        return new ActionListenerExample1Impl(this.serviceState);
    }

Note, we will not worry about action distribution priority for now, this topic will be covered in later guides.

The extension will also provide a prompt decorator, to customize the prompt. Here I have chosen a simple stateless prompt "> "; however, parts of the extension may place statefull objects in the SessionState to alter the state based on a profile or a command progression.

    public boolean providesPromptDecorator() {
        return true;
    }

    public PromptDecorator createPromptDecorator()
        throws PromptDecoratorCreateException {
        return new PromptDecorator() {
            public void initialize(SessionState sessionState)
                throws PromptDecoratorInitializationException {
            }

            public boolean decoratePrompt(SessionState sessionState, Prompt prompt) {
                prompt.append("> ");
                return true;
            }
        };
    }

We will play with OutputFormatSpi s a bit later on, so for now we won't provide any.

    public boolean providesOutputFormats() {
        return false;
    }

    public OutputFormatSpi[] getOutputFormats() {
        return null;
    }
}

We have provided the PromptDecorator above, now need to implement the ActionListener . [ActionListenerExample1Impl.java ]

class ActionListenerExample1Impl extends ActionListenerDefaultImpl implements
    ActionListener
{
    ServiceState serviceState;
    Map<String, Command> commands;

    ActionListenerExample1Impl(ServiceState serviceState) {
        this.serviceState = serviceState;
        

4 commands are added to a command lookup

        this.commands = new HashMap<String, Command>();
        addCommand(new HelpCommand());
        addCommand(new SayHelloCommand());
        addCommand(new CloseCommand());
        addCommand(new ShutdownCommand());
    }
    
    public void initialize(SessionState sessionState) throws ActionListenerInitializationException {
    }

    private void addCommand(Command command) {
        this.commands.put(command.getName(), command);
    }

The ActionListener is provided notification of what's happening on the console through notify* methods. We're interested in the newLine event. The rest should be easy to figure out, I'll just comment here and there :-)

    public boolean notifyNewLine(SessionState sessionState, String currentBuffer, int relOffset) {
        Logger logger = Logger.getLogger(this.getClass());
        logger.info("Got line [" + currentBuffer + "]");

        // simple command parsing, nothing fancy
        int space = currentBuffer.indexOf(' ');
        String commandStr = null;
        String tail = null;
        if (space>0) {
            commandStr = currentBuffer.substring(0, space);
            tail = currentBuffer.substring(space+1);
        }
        else {
            commandStr = currentBuffer;
            tail = "";
        }
        // find command
        Command command = this.commands.get(commandStr);
        if (command == null) {

The console delegate provide access to the console output. Here we use it to output text to the user.

            ConsoleDelegate consoleDelegate = sessionState.getSession().getConsoleDelegate();
            consoleDelegate.writeln("Unknown command, try 'help'");
        }
        else {
            command.execute(sessionState, tail);
        }
        return true;
    }

    static interface Command
    {
        String getName();

        String getDescription();

        void execute(SessionState sessionState, String tail);
    }

    class HelpCommand implements Command
    {

        public String getName() {
            return "help";
        }

        public String getDescription() {
            return "display this help message";
        }

        public void execute(SessionState sessionState, String tail) {
            StringBuffer buf = new StringBuffer();

            ConsoleDelegate console = sessionState.getSession().getConsoleDelegate();

            for (Command c : commands.values()) {
                buf.append(c.getName()).append(": ").append(c.getDescription()).append(console.getEOLN());
            }

            console.writeln(buf.toString());
        }
    }

    class SayHelloCommand implements Command
    {
        public String getName() {
            return "sayhello";
        }

        public String getDescription() {
            return "try 'sayhello <name>'";
        }

        public void execute(SessionState sessionState, String tail) {
            ConsoleDelegate consoleDelegate = sessionState.getSession().getConsoleDelegate();
            consoleDelegate
                .writeln("Hello, " + tail + "!");
        }
    }

    class CloseCommand implements Command
    {
        public String getName() {
            return "close";
        }

        public String getDescription() {
            return "exit this session";
        }

        public void execute(SessionState sessionState, String tail) {

The SessionState provides a way to request session disactivate:

            sessionState.disactivateSession();
        }
    }

    class ShutdownCommand implements Command
    {
        public String getName() {
            return "shutdown";
        }

        public String getDescription() {
            return "shutdown the server";
        }

        public void execute(SessionState sessionState, String tail) {
            ConsoleDelegate consoleDelegate = sessionState.getSession().getConsoleDelegate();
            consoleDelegate.writeln("System shuting down. . .");
            
            serviceState.isRunning = false;
        }
    }

}

Step 4: Registering a TelnetServiceExtension

The newly created extension is registered with the service to be activated. In our example, it happens in the main initialize() method:

    public void initialize() {
        this.serviceState = new ServiceState();

        this.telnetService = new TelnetService();
        this.telnetService.setBindPort(2323);

        this.telnetService.unregisterAllExtensions();
        this.telnetService.registerExtension(new TelnetServiceExtensionExample1Impl(this.serviceState));
    }

The service comes with a default extension that provides a default prompt decorator (similar to that we have written here. We have called the unregisterAllExtensions() here for the sake of demonstration; however, the default extension may be found useful to keep in other situations as it also provides a catch-all for the OutputFormatSpi feature.

The extension is now registered, let's take it for a small ride.

Compile and run, then use your favorite telnet client to connect to localhost:2323:

> help
close: exit this session
shutdown: shutdown the server
sayhello: try 'sayhello <name>'
help: display this help message

> sayhello Jojo
Hello, Jojo!
> shutdown
System shuting down. . .

Step 5: Adding some colors

Although work is currently being done on the output formatting features to simplify their usage, the current state already allows for some nice output coloring (in a VT100 or higher compatible client).

Let's apply some colors to the 'help' output. We will create an implementation of the OutputFormatSpi interface and register it (this will most probably become a feature that is provided by the extension on registration) [OutputFormatExample1Impl.java ]:

class OutputFormatExample1Impl extends OutputFormatVT100Impl implements OutputFormatSpi
{
        @Override
        public boolean formats(Object subject) {
                return subject instanceof Command;
        }
        
        @Override
    public StringBuffer format(SessionState sessionState, StringBuffer buffer, Object subject) {
        if (subject instanceof Command) {
            Command command = (Command)subject;
            String eoln = sessionState.getSession().getConsoleDelegate().getEOLN();
            

Please read on the usage of OutputFormatVT100Impl in a href="/telnetservice-core/apidocs/fr/gedeon/telnetservice/OutputFormatVT100Impl.html" its javadoc pages/a .

In brief, we include attributes that specify the forground color, background color and style of the chars. Here we apply a green underscore to the name, and a plain yellow to the description.

            setAttrs(buffer, new byte[][] { ATTRS_FORE_GREEN, STYLE_UNDERSCORE });
            buffer.append(command.getName());
            resetAttrs(buffer);
            setAttrs(buffer, new byte[][] { ATTRS_FORE_YELLOW });
            buffer.append(": ").append(command.getDescription());
            resetAttrs(buffer);
            buffer.append(eoln);
        }
        else {
            buffer.append(subject);
        }
        return buffer;
    }
}

We update the help command execution code to use the OutputFormat instance.

    class HelpCommand implements Command
        // ...
        
        public void execute(SessionState sessionState, String tail) {
            StringBuffer buf = new StringBuffer();

            ConsoleDelegate consoleDelegate = sessionState.getSession().getConsoleDelegate();
            OutputFormat outputFormat = sessionState.getSession().getOutputFormat();

            for (Command c : commands.values()) {
                buf = outputFormat.format(sessionState, buf, c);
            }

            consoleDelegate.writeln(buf.toString());
        }

And make sure to update the extension to provide it:

public class TelnetServiceExtensionExample1Impl implements TelnetServiceExtension
{
    ServiceState serviceState;
    OutputFormatSpi[] outputFormats;
    
    public TelnetServiceExtensionExample1Impl(ServiceState serviceState) {
        this.serviceState = serviceState;
        this.outputFormats = new OutputFormat[] { new OutputFormatExample1Impl() };
    }
    
    // ...
    
    public boolean providesOutputFormats() {
        return true;
    }

    public OutputFormatSpi[] getOutputFormats() {
        return this.outputFormats;
    }

Let's try it:

Download the Example Source

The complete example sources (telnetservice-example1-core ) can be downloaded at the project download section .

Conclusion

During this short tutorial you have integrated telnet functionality to your application in a matter of a couple hour's work. Have gotten a sure impression of what it can provide, and will take a moment to really design a command structure that best fits your customers' expectations.

In planned releases for this library are, among others:

  • Java Authentication and Authorization Service (JAAS) login module incorporation: this feature will allow the integration with any of the many available login module implementations (including JNDI, Oracle, etc)
  • Declarative command structure and command abstraction: which will provide the integrating application a way to describe the commands they want to provide, and only implement the reaction to those commands, rather than the tokenizer, parser and syntax analyser, which would be provided.