tags:

views:

406

answers:

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)

A: 

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
+2  A: 

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
I tried console.print() per your suggestion, but this seems to send output to System.out of my application, not the console :-/
sanity
you'll need to use the console that you're exposing on telnet, that interpreter initialization is the default non interactive one
Lorenzo Boccaccia
How do I get the console that I'm exposing on telnet, given the code I added to my question?
sanity
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
A: 

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_D