Back in the days of WebLogic v8.x, when BEA still owned it and wlst wasn’t the official scripting tool yet, the unofficial wlshell and wlshell Explorer tools were available along with the official command-line tool weblogic.Admin. wlshell was a Windows utility and offered a GUI interface to browse the MBeans similar to the Windows Explorer. Later, wlshell was advantageously superseded by wlst, but its companion command wlshell Explorer just disappeared since 2014 and is nowhere to be found. Even Yahoo has no clue about what it has become.
The WebLogic Scripting Tool, or wlst for short, implements a file system analogy to navigate or edit a WebLogic domain’s MBeans. Those classes are presented as directories, and their attributes and methods as files. Navigation commands such as cd, ls, pwd allow to move intuitively into the configuration, edition, jndi or run-time trees, which are seen as drives. Once the appropriate tree has been chosen and an MBean selected, one can set its attributes, create other beans, and invoke methods as well, all this from the command-line interface provided by the jython interpreter. Commands can also be saved in a text file as a script for later re-execution. All this is quite effective except when one is not very familiar with the trees or, when developing a script, one needs to quickly move up and down the MBeans hierarchy, e.g. to look for some class or property. Indeed, wlst uses python syntax for its commands, which is a bit verbous. For example, changing to the MBean Servers/my_managed_server/Log/my_managed_server requires typing the command cd(‘/Servers/my_managed_server/Log/my_managed_server’), complete with parentheses and quotes but no parameter completion; listing the current object’s content requires ls(), with the mandatory parentheses. Admittedly, there is a command history but still, after a while of typing one’s way through the directories, it becomes quite boring. In some cases, command and parameter completion would help but a graphical, point-and-click interface would be even better and this is what this article will present.
To be complete, a generic GUI-based tool for navigating run-time MBeans and more already exists: jconsole. This tool actually can attach itself to mostly any process running a JVM, e.g. a node manager, a managed server, wlst and jconsole itself, and display run-time information e.g. about memory and threads; it can even force a garbage collection. Run-time MBeans are also browsable but require quite a few clicks to reach their attributes and the display looks a bit crowded. As a run-time monitoring tool, jconsole does not browse the configuration trees but only the run-time ones.
In this article, I introduced such a tool for navigating a Documentum repository and sending the output into an HTML page. It uses the python SimpleHTTPRequestHandler module but replaces the calls to the filesystem with calls to the DfCs API and the result was quite effective; at least, the realization was quick and easy. So I revisited the module and applied the same approach but this time with invocation of wlst commands, mostly ls(), cd() and cmo. The result of this is a graphical mbeans navigation utility that wraps wlst commands’output into an HTML page for a browser to display. Let’s see how this hack was done.

Installation

The SimpleHTTPServer module, along with its parent module BaseHTTPServer are both included in WebLogic’s jython. However, instead of fiddling with WebLogic’s original jars, let’s download the latest sources from github. Unfortunately, they won’t work with the old jython 2.2.x distributed with the WebLogic Server up to v12.2.x because they are part of the standard jython 2.7.2. distribution, so we need a more recent jython. Fortunately, the latest WebLogic v14.1.1.0 has its embedded jython upgraded from v2.2.1 to v2.7.1, cf. here. Thus, WL v14.1.1.0 is a requirement and a 212 Mb slim and lite version of WL can be easily grabbed and installed from Oracle Fusion Middleware Software Downloads (an Oracle account is needed).
It goes without saying that a JDK is also needed; we’ll download a JDK v11 from Java SE Development Kit 11 Downloads; the tar ball package is just fine for we’ll install it locally.
Let’s set up a working directory for all the above, and work as user oracle:

export work_dir=~/wlst-explorer
cd
mkdir $work_dir
cd $work_dir

# JDK step;
tar xvf ~/Downloads/jdk-11.0.11_linux-x64_bin.tar.gz
export JAVA_HOME=${work_dir}/jdk-11.0.11
export PATH=${JAVA_HOME}/bin:$PATH

# the fmw software will be installed in ${work_dir}/wls1411 by default;
java -jar  ~/Downloads/fmw_14.1.1.0.0_wls_lite_quick_generic.jar

# get the jython http server modules;
wget https://raw.githubusercontent.com/jython/jython/master/Lib/BaseHTTPServer.py
wget https://raw.githubusercontent.com/jython/jython/master/Lib/SimpleHTTPServer.py

Once downloaded, wlst and the http modules can be checked thusly:

export ORACLE_HOME=${work_dir}/wls1411
export PATH=${ORACLE_HOME}/oracle_common/common/bin:${PATH}
wlst.sh
Initializing WebLogic Scripting Tool (WLST) ...

Welcome to WebLogic Server Administration Scripting Shell

Type help() for help on available commands

wls:/offline> version
u'WebLogic Server 14.1.1.0.0'
wls:/offline> execfile('/home/oracle/wlst-explorer/SimpleHTTPServer.py')
Serving HTTP on 0.0.0.0 port 8000 ...

Now, point your browser to http://localhost:8000 and start browsing the local file system starting from the current directory. If you want to display the parent directory and up, you need to stop and relaunch wlst from there.
Alternatively, wlst can execute the module directly from the command-line:

wlst.sh /home/oracle/wlst-explorer/SimpleHTTPServer.py

#Type ^C to exit wlst.

If the browser displays the directory entries, then it means that the installation of wlst was fine and that it can execute jython 2.7.1 code.
By default, wlst starts in its JVM with only a maximum heap size of 1Gb. That is plenty enough to browse but as we’ll see it later, it may not suffice in some cases.
An alternative to installing wls_14.4.1.0 just to have jython 2.7.1 is to proceed the other way around, i.e. install a stand-alone jython 2.7.1 and import wlst as a jython module from an existing WebLogic installation. I couldn’t make it work however, probably because of some missing jars, therefore let’s stick with the first alternative.

The changes

First, the following small modification must be done in the base module BaseHTTPServer.py

diff BaseHTTPServer.py_original BaseHTTPServer.py
591c591,592
          ServerClass = HTTPServer, protocol="HTTP/1.0",
>          server_address = ('localhost', 8000)):
594,595c595
<     This runs an HTTP server on port 8000 (or the first command line
     This runs an HTTP server listening on the given server_address address and port (default is localhost:8000);
599,604d598
<     if sys.argv[1:]:
<         port = int(sys.argv[1])
<     else:
<         port = 8000
<     server_address = ('', port)
< 

In line 4, in the test() function, the server_address to listen to has been made a tuple of values, the server address and the port, where previously the function read itself the port from the command-line. BaseHTTPServer.test() is invoked by SimpleHTTPServer.test() with all the sensible parameters passed to it so it does not need to get the port itself any more.
The biggest changes were obviously done in the SimpleHTTPServer module. As that module is not that large, and the changes mainly consist of removed code, I'll list the complete module's source code here instead of the diffs:

# A web page to navigate a WebLogic wlst trees.
# Based on jpython's team SimpleHTTPServer.py module https://github.com/jython/jython/blob/master/Lib/SimpleHTTPServer.py;
# cec at dbi-services.com, May 2021, integration with WebLogic's wlst;
# E.g:
#     /oracle/wls1411/oracle_common/common/bin/wlst.sh SimpleHTTPServer.py --bind_port_pyserver 10000 --wl_admin_server_url=t3://localhost:7001 \
#                                                      --wl_admin_user weblogic --wl_admin_pwd welcome1 --wl_tree domainRuntime

trees = ['custom', 'domainConfig', 'domainCustom', 'domainRuntime', 'edit', 'editCustom', 'jndi', 'runtime', 'serverConfig', 'serverRuntime']

def clean_mbean_name(mbean_name):
   mbean_name = str(mbean_name)
   return mbean_name[mbean_name.find(']') + 1 :]

import traceback
def list_dir(path):
   """
   return a list of dictionaries, one for each  entries in directory path, e.g.:
      [
         {'is_directory': True, 'name': u'mydomain', 'value': None, 'path_name': '/AdminConsole/', 'value': '....' | None, 'mbean_name': '....' | None}
         ...
      ]
   """
   entries = []
   try:
      if '...' == path[-3:]:
         cd(path[:-1])
         path = pwd()
      else:
         cd(path)
         path = pwd()
   except:
      print('exception in list_dir() while cd-ing to ' + path)
      dumpStack()
      traceback.print_exc()

   # remove tree prefix;
   for t in trees:
      if 0 == path.find(t + ':'):
         path = path[path.find(':') + 1:]
         break

   current_mbean = clean_mbean_name(getMBean(path))
   try:
      cd(path)
      for p in ls().split('\n'):
         if not p:
            continue
         p = p.strip()
         if "" == p:
            continue
         if p[0] not in ("d", "-"):
            # continuation of previous value;
            entries[-1]['value'] += p
            continue
         if 'd' == p[0]:
            entry = {'is_directory': True, 'path_name': path, 'name': p[7:], 'value': None}
            entry['mbean_name'] = clean_mbean_name(getMBean(path + ('/' if '/' != path else '') + entry['name']))
         elif -1 == p.find(' ', 7):
            entry = {'is_directory': False, 'path_name': path, 'name': p[7:], 'value': None, 'mbean_name': None}
         else:
            entry = {'is_directory': False, 'path_name': path, 'name': p[7:p.find(' ', 7)], 'value': p[p.find(' ', 7):].strip(), 'mbean_name': None}
         entries.append(entry)

         if entry['is_directory']:
            current_mbean = clean_mbean_name(getMBean(path + "/" + entry['name']))
   except:
      print('exception in list_dir() while ls-ing ' + path)
      dumpStack()
      traceback.print_exc()

   return (entries, path)
 
# needed because we need the set() constructor but it is overriden by wlst;
import __builtin__
def sweep_dir(path, (cumul, visited) = (None, None)):
   """
   recursively, breadth-first lists path content and return a list of raw entries;
   path is a clean path string, e.g. /AdminServer;
   an attempt at preventing infinite recursion is done but there is a risk that some entries are missed;
   """
   if (cumul, visited) == (None, None):
      (cumul, visited) = ([],  __builtin__.set())
   try:
      temp_ls, path = list_dir(path)
   except:
      print('exception in sweep_dir()')
      dumpStack()
      traceback.print_exc()
      print(404, "error while listing directory " + path + ", ignoring request ...")
      return None

   current_mbean = clean_mbean_name(getMBean(path))

   # display attributes first so they are grouped together right below their parent directory;
   try:
      for p in temp_ls:
         if p['is_directory']:
            continue
         cumul.append(p)

      # display the sub-directories now;
      for p in temp_ls:
         if not p['is_directory']:
            continue
         cumul.append(p)
   
         for p in temp_ls:
            if p['is_directory']:
               if p['mbean_name'] != current_mbean and p['mbean_name'] in visited:
                  print('mbean ' + p['mbean_name'] + ' already visited, skipping ...')
                  continue
               else:
                  visited.add(p['mbean_name'])
                  sweep_dir(path + ('/' if '/' != path[-1] else '') + p['name'], (cumul, visited))
   except:
      dumpStack()
      traceback.print_exc()
   return cumul

"""Simple HTTP Server.

This module builds on BaseHTTPServer by implementing the standard GET
and HEAD requests in a fairly straightforward manner.

"""
__version__ = "0.6"
__all__ = ["SimpleHTTPRequestHandler"]

import posixpath
import BaseHTTPServer
import urllib
import cgi
import sys
import shutil
import traceback
try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO

class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):

    """Simple HTTP request handler with GET and HEAD commands.

    The GET and HEAD requests are identical except that the HEAD
    request omits the actual contents of the file.

    """

    server_version = "SimpleHTTP/" + __version__

    def do_GET(self):
        """Serve a GET request."""
        f = self.send_head()
        if f:
            shutil.copyfileobj(f, self.wfile)
            f.close()

    def do_HEAD(self):
        """Serve a HEAD request."""
        f = self.send_head()
        if f:
            f.close()

    def send_head(self):
        """Common code for GET and HEAD commands.

        This sends the response code and MIME headers.

        Return value is either a file object (which has to be copied
        to the outputfile by the caller unless the command was HEAD,
        and must be closed by the caller under all circumstances), or
        None, in which case the caller has nothing further to do.

        """
        path = posixpath.normpath(urllib.unquote(self.path))
        # ignore those pesky requests to favicon from the browser because they cause wlst errors;
        if '/favicon.ico' == path:
            return None
        return self.list_directory(path)

    def list_directory(self, path):
        """Helper to produce a directory listing (absent index.html).

        Return value is either a file object, or None (indicating an
        error).  In either case, the headers are sent, making the
        interface the same as for send_head().

        """
        start_time = System.currentTimeMillis()
        try:
            if '!' == path[-1]:
               path = path[:-1]
               list = sweep_dir(path)
               b_recursive = True
               # hack for the mysterious java.lang.NullPointerException error after a full tree's recursive scan;
               cd('..'); cd('/')
            else:
               list, path = list_dir(path)
               b_recursive = False
        except:
            self.send_error(404, "error while listing directory " + path + ", ignoring request ...")
            traceback.print_exc()
            return None

        self.path = path
        end_time = System.currentTimeMillis()
        print("query took %10.1f seconds" % ((end_time - start_time)/1000))
        f = StringIO()
        displaypath = cgi.escape(urllib.unquote(self.path))
        f.write('')
        f.write("\nDirectory Listing for %s\n" % displaypath)
        f.write('\n\n\n')
        f.write("\n

Directory listing for %s: %s

\n" % (args.wl_tree, displaypath)) f.write('
\n
    \n') if '' != path and '/' != path: f.write('%s' % (urllib.quote(path + '/...'), '..')) for entry in list: name = entry['name'] displayname = linkname = name # Append / for directories; if entry['is_directory']: displayname = entry['path_name'] + ('/' if '/' != entry['path_name'][-1] else '') + name if b_recursive else name linkname = entry['path_name'] + ('/' if '/' != entry['path_name'][-1] else '') + name f.write('
  • %s%s\n' % (b'\xf0\x9f\x93\x81', urllib.quote(linkname), cgi.escape(displayname))) else: value = entry['value'] if entry['value'] else '' f.write('
  • %s%s\n' % (b'\xe2\x88\x99', cgi.escape(displayname) + (cgi.escape(value) if not value else '' + " " + cgi.escape(value) + ''))) f.write("
\n
\n\n\n") length = f.tell() f.seek(0) self.send_response(200) if not sys.platform.startswith("java"): encoding = sys.getfilesystemencoding() self.send_header("Content-type", "text/html; charset=%s" % encoding) self.send_header("Content-Length", str(length)) self.end_headers() return f def test(HandlerClass = SimpleHTTPRequestHandler, ServerClass = BaseHTTPServer.HTTPServer, bind_address = "localhost", bind_port = 8000): BaseHTTPServer.test(HandlerClass, ServerClass, server_address = (bind_address, bind_port)) import argparse import textwrap if __name__ == '__main__': parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description = textwrap.dedent("""\ A web page to navigate a WebLogic domain edit or runtime trees. Based on jpython's team SimpleHTTPServer.py module https://github.com/jython/jython/blob/master/Lib/SimpleHTTPServer.py; cec at dbi-services.com, May 2021, integration with WebLogic's wlst; E.g: /oracle/wls1411/oracle_common/common/bin/wlst.sh ~/Downloads/SimpleHTTPServer.py --bind_port_pyserver 10000 --wl_admin_server_url=t3://localhost:7001 --wl_admin_user weblogic --wl_admin_pwd welcome1 --wl_tree domainRuntime """)) parser.add_argument('--bind_address_pyserver', '-b', default = 'localhost', metavar = 'ADDRESS', help = 'Specify an alternate bind address for the python server [default: all interfaces]') parser.add_argument('--bind_port_pyserver', action = 'store', default = 8000, type = int, nargs = '?', help = 'Specify an alternate port for the python server [default: 8000]') parser.add_argument('--wl_admin_server_url', action = 'store', default = 't3://localhost:7001', type = str, nargs = '?', help = "Specify an WebLogic's domain admin. server [default: t3://localhost:7001]") parser.add_argument('--wl_admin_user', action = 'store', default = 'weblogic', type = str, nargs = '?', help = 'Specify an alternate WebLogic admin user [default: weblogic]') parser.add_argument('--wl_admin_pwd', action = 'store', default = None, type = str, nargs = '?', help = 'Specify an alternate WebLogic admin user password [leave empty to use generated key file]') parser.add_argument('--wl_tree', action = 'store', default = 'domainConfig', type = str, nargs = '?', choices = trees, # ['custom', 'domainConfig', 'domainCustom', 'domainRuntime', 'edit', 'editCustom', 'jndi', 'runtime', 'serverConfig', 'serverRuntime'], help = 'Specify the working tree [default: domainConfig]') args = parser.parse_args() # connect to the WebLogic domain using the provided credentials and admin server's url; if args.wl_admin_pwd: connect(username = args.wl_admin_user, password = args.wl_admin_pwd, url = args.wl_admin_server_url) else: connect(url = args.wl_admin_server_url) eval(args.wl_tree + '()') # show time; test(bind_address=args.bind_address_pyserver, bind_port=args.bind_port_pyserver)

The removed code was dealing with files to get their mime type and display them, which does not apply in the wlst’s filesystem metaphor because files are content-less attributes whose value are simply displayed in the html page.

Launching the mbean browser

As with the original source, the invocation can be as simple as:

$ wlst.sh ./SimpleHTTPServer.py

A help option is available to display the full invocation syntax:

wlst.sh ~/Downloads/SimpleHTTPServer.py --help
Initializing WebLogic Scripting Tool (WLST) ...

Welcome to WebLogic Server Administration Scripting Shell

Type help() for help on available commands

usage: SimpleHTTPServer.py [-h] [--bind_address_pyserver ADDRESS]
                           [--bind_port_pyserver [BIND_PORT_PYSERVER]]
                           [--wl_admin_server_url [WL_ADMIN_SERVER_URL]]
                           [--wl_admin_user [WL_ADMIN_USER]]
                           [--wl_admin_pwd [WL_ADMIN_PWD]]

A web page to navigate a WebLogic domain edit or runtime trees.
Based on jpython's team SimpleHTTPServer.py module https://github.com/jython/jython/blob/master/Lib/SimpleHTTPServer.py;
cec at dbi-services.com, May 2021, integration with WebLogic's wlst;
E.g:
    /oracle/wls1411/oracle_common/common/bin/wlst.sh ~/Downloads/SimpleHTTPServer.py --bind_port_pyserver 10000 --wl_admin_server_url=t3://localhost:7001 --wl_admin_user weblogic --wl_admin_pwd welcome1 --wl_tree domainRuntime

optional arguments:
  -h, --help            show this help message and exit
  --bind_address_pyserver ADDRESS, -b ADDRESS
                        Specify an alternate bind address for the python
                        server [default: all interfaces]
  --bind_port_pyserver [BIND_PORT_PYSERVER]
                        Specify an alternate port for the python server
                        [default: 8000]
  --wl_admin_server_url [WL_ADMIN_SERVER_URL]
                        Specify an WebLogic's domain admin. server [default:
                        t3://localhost:7001]
  --wl_admin_user [WL_ADMIN_USER]
                        Specify an alternate WebLogic admin user [default:
                        weblogic]
  --wl_admin_pwd [WL_ADMIN_PWD]
                        Specify an alternate WebLogic admin user password
                        [leave empty to use generated key file]
  --wl_tree [{custom,domainConfig,domainCustom,domainRuntime,edit,editCustom,jndi,runtime,serverConfig,serverRuntime}]
                        Specify the working tree [default: domainConfig]

By default, for security reason, bind_address_pyserver is localhost; use 0.0.0.0 if the server must listen to all the network interfaces, e.g. when a weblogic domain runs inside a VM but the browser runs in the host machine.
bind_port_pyserver defaults to 8000 but can be changed if it conflicts with a local weblogic server or some other local service.
wl_admin_server_url is the url to connect to a WebLogic server, typically an administration server. Once SimpleHTTPServer is started, it always points to the same domain but several instances of it can be started, each with its own bind address and/or port.
wl_admin_user and wl_admin_pwd are self-explanatory.
As the provided password is visible in the process list, a better connection alternative is to use a key file; in such a case, no user name nor password is needed any more but we need to generate that file first (actually, a couple of files), as shown below:

# use username and password for the first connection:
connect('weblogic', 'welcome1', 't3://localhost:7001')

# generated the key file in the default location, the user's home directory:
storeUserConfig()
Creating the key file can reduce the security of your system if it is not kept in a secured location after it is created. Creating new key...
The username and password that were used for this WebLogic Server connection are stored in /home/oracle/oracle-WebLogicConfig.properties and /home/oracle/oracle-WebLogicKey.properties.

# disconnect and try a passwordless connection:
disconnect()
Disconnected from weblogic server: AdminServer
connect('t3://localhost:7001')
Connecting to t3://localhost:7001 with userid weblogic ...
Successfully connected to Admin Server "AdminServer" that belongs to domain "webcenter".

Now, wlst can be started without credentials, e.g:

/oracle/wls1411/oracle_common/common/bin/wlst.sh ~/Downloads/SimpleHTTPServer.py --bind_port_pyserver 10000 --wl_admin_server_url=t3://localhost:7001

The tree to browse can be specified at startup time through the parameter ––wl_tree; it must be precisely one of custom, domainConfig, domainCustom, domainRuntime, edit, editCustom, jndi, runtime, serverConfig, serverRuntime (see help(‘trees’) in wlst), as defined on line 8 of the script. Its value is checked because of the eval() statement at line 287. We don’t want code injection, do we ?

Browsing the mbeans

Say we started SimpleHTTPServer with bind_port_pyserver set to 10000 on a host named oracle. The URL to load in the HTTP browser is: http://oracle:10000/. The administration server (or a managed server) listens on localhost:7001. Here is what is shows for a sample WebLogic domain:

The mbeans are shown as clickable folders, as hinted by the little icon on the left. If we scroll down a bit, we can see the properties of the current mbean with their value in green for better readability.

Let’s click a Servers and then on myserver:

Unquestionably, this point and click interface is much faster that laboriously and repeatedly calling jython functions, and visually more pleasing, albeit spartan, too.

Recursive browsing

As nice looking as SimpleHTTPServer is, if one is seaching a particular mbean or property, one can click like there is no tomorrow before finding it. WebLogic domains are simply that rich with deeply nested mbeans. In such a case, we could implement a find functionality with a query_string syntax but let’s keep things simple. Why not a recursive browsing of an mbean ? To keep up with wlst’s file hierarchy analogy, we would like a ‘ls -R’ functionality.
The syntax http://…./../! recursively explores the selected tree starting with the given mbean using a breadth-first search and outputs the result with the properties first, followed by the mbeans, then again with the exploration of the first found mbean, and so on. This way, the properties are listed right under their parent mbean in a flat presentation. Here is an illustration:

Here is shorter example:

A full domain exploration is very useful for those cases when one has only a vague idea of the name or location of an mbean or property in the config trees and wlst’s own find() command is ineffective (e.g. find() is not supported in the domainRuntime tree but is in the serverRuntime and domainConfig trees). As all of them are now listed in one html page, a simple ctrl-f (or whatever command it uses for searching in a page) in your browser can quickly save the day. If the request is sent by curl and redirected into a text file, more sophisticated commands such as egrep can be used too, and the file kept for future reference.
However, there are two caveats. The first one is the memory size used to hold a whole tree in memory. For simple domains with little services defined or application deployed, the default 1 Gb max heap size can be enough. If not, it can be increased by setting the environment variable USER_MEM_ARGS before launching wlst, as shown below:

$ export USER_MEM_ARGS='-Xmx2048m'
$ /oracle/wls1411/oracle_common/common/bin/wlst.sh ~/Downloads/SimpleHTTPServer.py --bind_address_pyserver 0.0.0.0 --bind_port_pyserver 10000 --wl_admin_server_url=t3://localhost:7103 --wl_admin_user weblogic --wl_admin_pwd welcome1 &
$ ps axjf | grep -i wlst
29113 18472 18472 29113 pts/4    18763 S      500   0:00      \_ /bin/sh /oracle/wls1411/oracle_common/common/bin/wlst.sh /home/oracle/Downloads/SimpleHTTPServer.py --bind_address_pyserver 0.0.0.0 --bind_port_pyserver 10000 --wl_admin_server_url=t3://localhost:7103 --wl_admin_user weblogic --wl_admin_pwd welcome1
18472 18475 18472 29113 pts/4    18763 S      500   0:00      |   \_ /bin/sh /oracle/wls1411/oracle_common/common/bin/fmwconfig_common.sh wlst_internal.sh /home/oracle/Downloads/SimpleHTTPServer.py --bind_address_pyserver 0.0.0.0 --bind_port_pyserver 10000 --wl_admin_server_url=t3://localhost:7103 --wl_admin_user weblogic --wl_admin_pwd welcome1
18475 18476 18472 29113 pts/4    18763 S      500   0:00      |       \_ /bin/sh /oracle/wls1411/oracle_common/common/bin/wlst_internal.sh /home/oracle/Downloads/SimpleHTTPServer.py --bind_address_pyserver 0.0.0.0 --bind_port_pyserver 10000 --wl_admin_server_url=t3://localhost:7103 --wl_admin_user weblogic --wl_admin_pwd welcome1
18476 18520 18472 29113 pts/4    18763 Sl     500   0:29      |           \_ /home/oracle/wlst-explorer//jdk-11.0.11/bin/java -DORACLE_HOME=/home/oracle/wlst-explorer/oracle_common -DORACLE_HOME=/home/oracle/wlst-explorer/oracle_common -Xmx2048m weblogic.WLST /home/oracle/Downloads/SimpleHTTPServer.py --bind_address_pyserver 0.0.0.0 --bind_port_pyserver 10000 --wl_admin_server_url=t3://localhost:7103 --wl_admin_user weblogic --wl_admin_pwd welcome1
29113 18764 18763 29113 pts/4    18763 S+     500   0:00      \_ grep -i wlst

The new value has indeed overridden the default one of 1024m that is set by commBaseEnv.sh.
Note also how convoluted the script wlst.sh is in this release: it calls fmwconfig_common.sh and wlst_internal.sh and finally starts a JVM, with several environments set in between from setWlstEnv_internal.sh, setHomeDirs.sh and commBaseEnv.sh. No need to preset an environment, it is taken care of by wlst.sh itself.

Recursively exploring a practically empty domain’s domainConfig tree with no services defined and only one administration server plus one managed server takes about 30 seconds on my old, faithful laptop and return more than 4’100 lines (directories and attributes), that a lot of information. By comparaison, the beefier demo WebCenter domain in a VM downloadable from Oracle takes about 30 minutes to be explored and returns about 27’000 entries. The same domain’s domainRuntime full exploration takes more than 2 hours and returns about 59’000 entries ! Eventhough some attributes are full multi-line xml content, that a lot of lines. Its jndi tree’s full dump takes 5900 lines and 20 minutes.

The second caveat is that the recursion finds itself in endless loops due to the fact that some mbeans are contained in or referenced by other mbeans. Consequently, when exploring the linked to mbeans, the exploration eventually loops back to an mbean it already visited before and from there it reaches again the linking mbean, and so forth, in an endless recursion. Examples of such problematic mbeans are CoherenceClusterSystemResource/defaultCoherenceCluster/Targets and the default myrealm. The first one embeds the managed servers; if those were already visited before (e.g. through https://oracle:10000/Servers/!), they would be visited again if no special safeguards were introduced, leading eventually to a never-ending loop. Thus, in order to prevent this while recursing, a breadcrumb trail is managed that tracks the already visited mbeans and keeps from revisiting them again and again (see lines 109 and 113 in SimpleHTTPServer above).

Conclusion

There are other alternatives for enhancing the wlst command output, including customizing wlst or even developing a full JMX client. Many such clients are also available for free and are just a download away, but most of them aim at run-time monitoring. The present one is taylormade for wlst and works with the configuration as well. If needed, it could even be enhanced to change the output format to something different from html, e.g. json, for monitoring. I hope this simple little utility will be helpful.