3

I'm using Beanshell as an embedded debugging tool in my app. It means I can telnet to my app and poke around with its internals while it is running (I typically wrap the telnet session with rlwrap).

The problem is that the only way I've found to print to the Beanshell console, rather than stdout of the application itself, is the print() method within Beanshell.

But I'd like to write code in Java that I can call from Beanshell, which will output to the Beanshell console - ie. it will be shown in my telnet session, not sent to stdout of the application, as happens if you try to use System.out or System.err.

Is this possible?


edit: To further clarify, I'm setting up a Beanshell server as follows:

public static void setUpBeanshell() {
    try {
        i.setShowResults(true);
        i.eval(new InputStreamReader(Bsh.class.getResourceAsStream("init.bsh")));
        i.eval("server(" + Main.globalConfig.beanShellPort + ");");
    } catch (final EvalError e) {
        Main.log.error("Error generated while starting BeanShell server", e);
    }
}

How would I modify this such that I can write a Java function that outputs to the telnet session (rather than to System.out of my application)

sanity
  • 35,347
  • 40
  • 135
  • 226
  • Can you inherit from the class in question, add the functionality that you desire, and instantiate an instance of that class instead? – Noctis Skytower Nov 25 '09 at 02:42
  • What about using a String dumpInfo() method on the object that would need to print on the standard output and then calling print(object.dumpInfo())? Or would you like it to work more like a logger? – Lorenzo Boccaccia Nov 25 '09 at 13:53
  • if you need it more logger like, you need to instantiate your interpreter on a specific console class constructed separately, and then objects could write to that console using console.print() - http://www.beanshell.org/javadoc/bsh/ConsoleInterface.html#print(java.lang.String) – Lorenzo Boccaccia Nov 25 '09 at 13:57

3 Answers3

2

I'll copy it there as it seems that comments are disregarded this days.

you can: instead of having a method which print debug information to the standard output returns that debug information:

class ClientList {
 Integer clients = 0;
 public String debugClientList() {
   return clients.toString();
 }

and then calling it from beanshell

print(clients.debugCientList());

will give you an output on your telnet

or if you need it more logger like, you need to interact with the Interpreter object directly

InterpreterSingleton {  
    public static final void Console console = new Interpreter();
}
....

class ClientList {
 Integer clients = 0;
 public void addClient(Client c) {
    ....
    InterpreterSingleton.console.print("Client added, clients now are " + clients);
 }

I'm replying there to the comment as it will need some more coding; the telnet implementation uses a different interpreter for each connection, so you have to expose that interpreter to the objects for printing to the telnet client. The quickest way is to change some bit in the default telnet server and use the modified one to start your server, instead of using the server() scripted command (it's under lgpl or sun license terms)

note that this way have an interpreter started for each connection; the easy and quick fix is to maintain a list of all the running interpreters and print to each one the debugging information, so:

class InterpreterSingletonList {  
    public static final void Set<Interpreter> is = new HashSet();
    void printToAll(String s) {
         for (Interpreter i: is) {
             i.print(s);
         }
    }
}



package bsh.util;

import java.io.*;

import java.net.Socket;
import java.net.ServerSocket;
import bsh.*;

/**
    BeanShell remote session server.
    Starts instances of bsh for client connections.
    Note: the sessiond effectively maps all connections to the same interpreter
    (shared namespace).
*/
public class Sessiond extends Thread
{
    private ServerSocket ss;
    NameSpace globalNameSpace;

    public Sessiond(NameSpace globalNameSpace, int port) throws IOException
    {
        ss = new ServerSocket(port);
        this.globalNameSpace = globalNameSpace;
    }

    public void run()
    {
        try
        {
            while(true)
                new SessiondConnection(globalNameSpace, ss.accept()).start();
        }
        catch(IOException e) { System.out.println(e); }
    }
}

class SessiondConnection extends Thread
{
    NameSpace globalNameSpace;
    Socket client;

    SessiondConnection(NameSpace globalNameSpace, Socket client)
    {
        this.client = client;
        this.globalNameSpace = globalNameSpace;
    }

    public void run()
    {
        try
        {
            InputStream in = client.getInputStream();
            PrintStream out = new PrintStream(client.getOutputStream());
            /* this is the one you're looking for */
                        Interpreter i = new Interpreter( 
                new InputStreamReader(in), out, out, true, globalNameSpace);
            i.setExitOnEOF( false ); // don't exit interp
                    /*store the interpreter on the list*/
                    InterpreterSingletonList.is.add(i);
            i.run();
                    /*remove it (i.run() blocks)*/
                    InterpreterSingletonList.is.remove(i);
        }
        catch(IOException e) { System.out.println(e); }
    }
}
Lorenzo Boccaccia
  • 6,041
  • 2
  • 19
  • 29
  • I tried console.print() per your suggestion, but this seems to send output to System.out of my application, not the console :-/ – sanity Nov 26 '09 at 16:26
  • you'll need to use the console that you're exposing on telnet, that interpreter initialization is the default non interactive one – Lorenzo Boccaccia Nov 26 '09 at 16:51
  • How do I get the console that I'm exposing on telnet, given the code I added to my question? – sanity Nov 27 '09 at 01:46
  • updated with the bsh telnet server code modified. change the class name or put it in another package, and start it instead of using server(port) inside a script. – Lorenzo Boccaccia Nov 30 '09 at 09:10
0

I think it's not possible with out hack..., sorry, adapting the telnet server implementation of BSH.

The class we're looking at is bsh.util.Sessiond. Once started, it opens and maintains a telnet server. When ever it receives a command, it creates a new worker thread, this on creates a new bsh.Interpreter with the correct input and output streams (derived from the socket) and runs the interpreter.

So it makes sense, that only output of interpreted commands is send to the telnet client, because System.out and System.err are not redirected.

But that exactly is what has to be done in your case: redirect System.out and System.err to the sockets output stream before the interpreter runs the command and reset the streams after completion.

I'd suggest, you copy the bsh.util.Sessiond class to something like mybsh.util.DebuggerSessiond, apply the redirection code to the run method of the inner class SessiondConnection and modify bsh/commands/server.bsh to start this 'new' telnet server in addition (or instead of the original one). (I guess, this script starts the servers...)

Source code can be found here: beanshell repository

Andreas Dolk
  • 113,398
  • 19
  • 180
  • 268
-1

If all your application's output is written using some logging framework anyways, you could write a custom appender/handler which besides logging to say a file would write to the beanshell console in addition? Possibly enabling and disabling the console-logging after executing some beanshell command.

(I wasn't aware of beanshell before, but it seems useful!)

Stefan L
  • 1,529
  • 13
  • 20