Return Home
Downloads
Project Archive
Articles Archives
Discussion
Contact Us
Contribute
Links

Using Buju

Client Connections   Adding Classpaths   Using Your Classes   Admin Commands   Localization  

Sorry, no quick-start. Using Buju effectively requires you to write code in your own applications to communicate with Buju. This is rarely done quickly. But then again, it's not really that difficult.

Reading the following sections can give you a basic understanding of how to use Buju, and make the details covered in the Buju documentation more comprehensible. After reading these sections, you should also do the Tutorial.

Client Connections

Writing Client Connections   Testing Client Connections  

Writing Client Connections

Buju is meant to be used by non-Java programming languages or application environments to access Java classes. Access occurs by means of TCP/IP communications over a socket connection, using SSL by default. Every programming language has its own way of accomplishing such a connection. Some samples in different languages appear further on.

Once a connection to Buju is established, the protocol is simple:

  • The client sends a request message.
  • The client receives a response message.

A message always takes the following 2-part form:

  • A 4-byte signed integer (Java int) indicating the length in bytes of the second part of the message, followed by
  • A string of bytes, which is the real content of the message.

Messages sent from the client to the server always have a positive integer as the first 4 bytes of the message.

Messages received by the client from the server can have a positive or negative integer value as the first 4 bytes of the message. The absolute value of the integer is always the length of the second part of the message. A positive value indicates that the message contains the successful evaluation (result of execution) of the client's request. But a negative value indicates that the message is reporting an exception or error in evaluating or executing the client's request. The content of an exception or error message describes the exception or error.

That's it for the protocol.

Here are the essential things a client must do to talk to Buju, in pseudo-code:

  create a socket connection to the Buju host and port (SSL by default)
  sendMessage("user=userID passwd=password [other login info]")
  receiveResponse()
  for (life of connection)
  {
    sendMessage("javacode")
    receiveResponse()
    if (response was an exception)  //i.e., leading int was negative
    {
      deal with failure
    }
    else
    {
      deal with success
    }
  }
  sendMessage(".close()")   //close connection gracefully
  receiveResponse()
  close socket connection

The string of login information sent to Buju is simply a series of name=value properties. A single space separates each property from the next. Properties can be in any order. Usage is:

  user=userID passwd=password [cset=character_set [myProp1=myValue1 [myProp2=myValue2] ...]] 

Two properties are mandatory:

  user=userID
  passwd=password

The cset property is optional. It is used to indicate to Buju that all communication to and from the client should henceforth be encoded in the named character set. (Note: The initial login string MUST be in the default character set of the server Buju is running on.) This allows a client using a character set of "windows-1252" (Windows Latin-1), for example, to communicate with a server using a character set of "US-ASCII" (American Standard Code for Information Interchange). Character set names are the canonical names for java.nio API. If the client and server are using the same default character set, which is usually the case, this property setting should be omitted.

All other properties provided in the login string are treated as user-defined properties and loaded into the connection's Map of properties (see getProperties()), for probable use through a custom admin command. User properties which omit the '=value' are loaded with a null value.

Here is some of the actual Java code from the included biz.bbeans.bujutest.BTest source used for making a connection:

  private void connect(String host, int port, SocketFactory sf)
  throws  UnknownHostException,
          IOException
  {
    socket = sf.createSocket(host, port);
    socket.setSoTimeout(30000);   //30 second timeout
    socket.setTcpNoDelay(true);
    in = new DataInputStream(socket.getInputStream());
    out = new DataOutputStream(socket.getOutputStream());    
  }
  
  public String login(String user, String passwd)
  throws  IOException,
          Exception
  {
    return eval("user=" + user + " passwd=" + passwd, null);
  }
  
  public String eval(String source, String charset)
  throws  IOException,
          Exception
  {
    String result = "";
    byte[] b = null;
    if (charset == null)
    {
      b = source.getBytes();
    }
    else
    {
      b = source.getBytes(charset);
    }
    out.writeInt(b.length);
    out.flush();
    out.write(b);
    out.flush();
    int len = in.readInt();
    b = new byte[Math.abs(len)];
    int read = in.read(b);
    if (read != b.length)
    {
      throw new IOException("Unexpected read exception.");
    }
    if (charset == null)
    {
      result = new String(b);
    }
    else
    {
      result = new String(b, charset);
    }
    
    if (len < 0)
    {
      throw new Exception(result);
    }
    return result;
  }

Most of the work occurs in the eval method. It sends a message (source) to the server and returns the server's response (result).

Here are similar code fragments written in BBx, an interpreted Business Basic language published by Basis, International:

  rem "N0 is pre-defined as an SSL socket device
  open_connection:
    u = unt; open (u,mode="host=localhost,port=2183")"N0"
    result$ = fneval$("user=userID passwd=password")
    ...
    
  eval_string:                                                       
    result$=fneval$(source$,err=eval_string_err)
    ...
    
  def fneval$(source$)
    x$ = source$, x$=bin(len(x$), 4) + x$                           
    write record(u)x$                                               
    result$ = ""
    dim x$:"len:i(4)"
    read record(u,siz=4,tim=30)x$ 
    if x.len <> 0 then read record(u,siz=abs(x.len),tim=5)result$  
    if x.len < 0 then fnerr 20       
    return result$                                                 
    fnend                                                      

You can find pre-written clients in various languages here. If you don't find something to suit your needs, you may want to write your own Buju test application which does the same as biz.bbeans.bujutest.BTest or one of the other donated clients. Please, contribute your examples written in your language.

Testing Client Connections

When testing client connections you should:

  • Make sure you can ping the server from the client, and the client from the server. You can use either IP addresses or resolvable names. If you can't ping, you have a network problem which will probably prevent proper client access to Buju.
  • Make sure there is no firewall restriction or virus detection interference preventing access by the client to Buju's server port (by default, port 2183).
  • Make sure the client has been added to buju/conf/bujuHosts.bsh. Either the client IP address or name is sufficient.
  • Try launching the included test client on the client computer. The test client is packaged in buju/lib/bujutest.jar. The client computer must have a Java runtime installed. Copy the buju/test directory to the client computer. Then on the client, launch the client test application by starting it at a shell prompt in the test directory with:

      java -Djavax.net.ssl.trustStore=clientstore -jar bujutest.jar
  • When the application window appears, fill in the connection information with appropriate values. Then click the "Connect" button.



  • This should gain you remote access to Buju.
  • Try evaluating some Java code.
  • When you are finished testing, delete the test directory from the client computer. For security reasons, you probably do not want an interactive application that allows someone to run any Java code he or she wants on your server. That's why Buju is an application-to-Java server, not a human-to-Java server.

Adding Classpaths

Using Buju in any meaningful way requires you to make classes, packages, and resources available to the Java interpreter(s). This is done by adding directories and jar files to the interpreter classpath.

Add classpaths in one of three ways:

  • Add them in your interpreter initialization script (the default script is buju/scripts/bujuInterp.bsh) with the addClassPath() command. This is the usual way to make classes and resources available to the interpreters.
  • Add them on the fly by sending the addClassPath() command once connected to the server.
  • Add them in your admin initialization script (the default is buju/scripts/bujuAdmin.bsh) with the addClassPath command. This is the usual way to make classes available to the admin interpreter only.

You could also add classpaths in buju/conf/wrapper.conf as a wrapper.java.classpath property, but this is the least desirable method to expand your classpath.

Using Your Classes

You can use the packages and classes you make available to the interpreter(s) by sending normal Java code to the interpreter through the Buju server, instantiating objects and making them work for you just as if you were programming in Java. The difference is that your Java code is interpreted as you send it to Buju.

The actual interpreter engine inside Buju is the open source Java application called BeanShell. A portion of any donation made to support Buju development goes to the BeanShell project. BeanShell is now packaged as a standard part of many free and commercial software products, including NetBeans. Please read the BeanShell documentation for a full explanation of their software. You may want to play around with BeanShell directly by launching the interactive BeanShell desktop window; do this by launching buju/lib/bsh.jar and typing in Java commands and creating Java objects. You can download the latest version of BeanShell from www.beanshell.org.

Tips for Debugging

  • Remember that the buju/logs/wrapper.log normally captures Java System.out and System.err output.
  • Setting verbose=true in buju/conf/buju.properties and restarting the Buju server will cause the server to spew received and sent messages and a few other useful things to Java System.out. Be careful to erase the verbose log when you are finished, because one of the things it logs are user ID's and passwords.
  • Using BeanShell interactively, try instantiating your Java application or running your BeanShell script. You can launch the BeanShell interactive desktop by executing the buju/lib/bsh.jar file.
  • Once you can successfully test your Java application interactively directly through BeanShell, try doing the same thing using one of the Buju Test applications.
  • If you have written your own Buju test application in your own language, test there also.
  • Finally, try your Java application from within your own target application.

Programming Strategy

Generally speaking, it is a good strategy to:

  • Place as much execution into Java classes as possible.
  • Place as much execution of interpreted Java into server scripts as possible.
  • Limit your network exchanges to as few as possible.

    Admin Commands

    Admin commands are special method invocations that start with a dot (.) which the client can send to Buju. Admin commands are scripted Java methods executed within the special and unique admin interpreter. The admin interpreter is initialized by a script (by default, buju/scripts/bujuAdmin.bsh). The admin initialization script defines a number of methods inside an enclosing "namespace" called admin(). All of the admin() methods take String's or numbers for parameters and all return a String result. These methods are what actually get executed when Buju receives an admin command. When a client wants to invoke an admin method, it does so by name, but prepends the name with a dot (.). For example, to invoke the admin method admin.close(), the client would send the command .close().

    So, from a client's perspective, these are the admin commands available:

    .status ( )
    Get the Buju server status.

    .version ( )
    Get the Buju version.

    .pauseServer ( String adminPassword )
    Pause the server from accepting connections.

    .resumeServer ( String adminPassword )
    Resume the paused server.

    .refreshServer ( String adminPassword )
    Refresh the server's data. Reloads buju/conf/bujuAccess.properties, buju/conf/bujuHosts.bsh, and buju/scripts/bujuInterp.bsh.

    .restartServer ( String adminPassword )
    Restart the server by creating a new server instance.

    .stopServer ( String adminPassword )
    Stop the server permanently from accepting any more connections.

    .setLocale ( String language , String country )
    Set the locale for the connection. Typically called immediately by the client if the server's default locale is not satisfactory.

    .close ( )
    Close the connection gracefully.

    .createPrivate ( String name )
    Create a private interpreter with the given name. The interpreter will persist until either explicitly destroyed or the connection terminates.

    .setPrivate ( String name )
    Set the named private interpreter as the connection's current interpreter. The interpreter will persist until either explicitly destroyed or the connection terminates.

    .destroyPrivate ( String name )
    Remove the named private interpreter from the list of private interpreters. If the interpreter is the connection's current interpreter, the connection can continue to use the interpreter with no problem. Once the connection loses a reference to the interpreter, or the connection terminates, the interpreter is eligible for garbage collection. All private interpreters created by a connection become eligible for garbage collection when the connection terminates.

    .createShared ( String name )
    Create a shared interpreter with the given name. A shared interpreter persists until destroyed. WARNING: any methods or variables in a shared interpreter's namespace are subject to multi-threaded access.

    .setShared ( String name )
    Set the named shared interpreter as the connection's current interpreter. WARNING: any methods or variables in a shared interpreter's namespace are subject to multi-threaded access.

    .destroyShared ( String name )
    Remove the named shared interpreter from the list of shared interpreters. This effectively makes the named interpreter unreachable by any connection that is not already using the interpreter. Any connections currently using the removed interpreter can continue to use it with no problem. Once the last connection loses a reference to the interpreter, it becomes eligible for garbage collection. Any connection can destroy an interpreter.

    .push ( )
    Push the current namespace of the connection's current interpreter onto the namespace stack, and create and set a new child namespace for the interpreter. The name of the new namespace is "n" + stack.size(). The new namespace will have visibility on all variables and methods that the parent namespace had. Any typed variable declarations will "shadow" a variable with the same name in the parent's scope. Any typed or new variable declarations will become invisible when the child namespace is popped, but any assignment of value to to a variable in the parent's scope will remain. Although this operation is not prohibited on shared interpreters, it would probably be very confusing to the sharers.

    .pop ( )
    Pop the last-in namespace from the namespace stack of connection's current interpreter and set it on the interpreter.

    .setTimeout ( long timeout )
    Set the connection's timeout, in milleseconds. The timeout value is used to start a timer for any thread wait() block. When the timer expires, the thread is interrupted if it is still blocked. Any shared interpreter will block at lock(), unlock(), and eval() if the interpreter is locked by another thread (connection). If a thread unblocks before the timer expires, the interrupt is cancelled.

    .lock ( String name )
    Lock the named shared interpreter using the connection's current timeout value.

    .lock ( String name , long timeout )
    Lock the named interpreter. If the lock is unavailable for more than the given timeout value, interrupt the thread. Although a private interpreter can be locked, there is no advantage, because it is only visible to one connection thread.

    .unlock ( String name )
    Unlock the named shared interpreter using the connection's current timeout value.

    .unlock ( String name , long timeout )
    Unlock the named shared interpreter. To unlock the interpreter the method will try to get the lock. If the lock is unavailable for more than the given timeout value, the thread is interrupted. Although a private interpreter can be locked and unlocked, there is no advantage, because it is only visible to one connection thread.

    .getLastThrowable ( )
    Get the last throwable encountered by the connection when it was evaluating source code.

    Full method documentation is contained in the admin initialization script (the default is buju/scripts/bujuAdmin.bsh). The script is easily customized, so you can add your own admin commands. The only requirement is that the method uses only String's, numbers and existing script variable names as parameters, and returns an object whose String.valueOf(object) results in a length greater than 0. It would be wise to use a method naming convention that won't conflict with future additions to the standard Buju release -- perhaps something like "myXXX()" where the "my" in the method name sets it apart from current and future standard admin command names.

    Localization

    Buju is enabled for localization of messages. US English translations of messages are provided by default, but we are hoping users will contribute message translations for other locales.

    There are two localization file hierarchies (called 'resource bundles') located in biz/bbeans/buju inside the buju/lib/buju.jar file:

    • BServer.properties: for messages originating from within biz.bbeans.buju.BServer
    • BServerAdmin.properties: for messages originating from within buju/scripts/bujuAdmin.bsh

    Most of the interesting messages are in the BServerAdmin.properties resource bundle.

    To add a new locale, using BServerAdmin.properties as an example:

    • Using the Java jar tool, extract BServerAdmin.properties from buju/lib/buju.jar to biz/bbeans/buju/BServerAdmin.properties.
    • Copy BServerAdmin.properties to BServerAdmin_la_co.properties, where 'la' is your language code and 'co' is your country code (e.g., BServerAdmin_de_DE.properties).
    • Editing your new locale properties file with a text editor, make your translations of the messages.
    • Save your changes.
    • Using the Java jar tool, jar up your new locale properties file into buju/lib/buju.jar.
    • Restart the server.
    • Send your translation to us.

    Explanation on use of the Java jar tool can be found on the Java web site.

    When the Buju server starts, it looks for a file in the appropriate resource bundle for the default locale of the server first, whatever that may be. If that is missing it ultimately falls back to the base file in the resource bundle, which contains US English translations.

    When a connection is made to the server, messages returned to the client from the server will be localized according to the server's locale. If the client wishes to have those messages localized for the client's locale:

    • A locale properties file must be added to the resource bundle biz/bbeans/buju/BServerAdmin.properties for the client's locale, as described above.
    • The client must issue the admin command .setLocale(language, country) to the Buju server once connected.
  • Copyright - All Rights Reserved. BBeans.biz

    Payback Feedback on this Project Documentation Suggested Use Download Project System Requirements Overview