Using the full potential of Jython to build compact and maintainable wsadmin scripts

Introduction

With the deprecation of Jacl as a supported language for wsadmin scripts in IBM WebSphere Application Server V6.1, Jython becomes the scripting language of choice. Most administrators who come to Jython from Jacl, perhaps via the conversion tooling supplied with the WebSphere Application Server Toolkit, continue to write Jython scripts as pseudo-Jacl scripts. Although this is a perfectly acceptable way of writing Jython scripts, there is much more in the language for script writers to use that can result in more compact, readable, and maintainable code. This article describes some of these advanced Jython features and provides several examples of how these features can be used in wsadmin scripts.

Jython indentation

Jython is a Java™ implementation of a language called Python. Python, created by Guido van Rossum in the early 1990s, is a high level, object-oriented language designed for readability and ease of use. Jython forces you to write neat code. Where languages such as Java and Jacl have curly brackets to delimit code blocks, Jython has indentation. Getting the indentation wrong in Java matters for maintainability, but the program will still work. If you get the indentation wrong in Jython, you will either get a run time error, or the code will not perform the way you expect. Jython V2.1 is the version of Jython that is supported in WebSphere Application Server V6.1.

In WebSphere Application Server, wsadmin is available for interactive and non-interactive (scripting) use. Many wsadmin users struggle to use it interactively because of the amount of typing or cutting and pasting that is sometimes needed to perform even straightforward updates. A by-product of using the techniques outlined in this article is that it becomes easier to use wsadmin in an interactive manner.

This article covers these topics:

  • Managing wsadmin attribute data using lists and dictionaries.
  • Using list comprehension and functional programming.
  • Providing flexible function interfaces.
  • Leveraging dictionaries and flexible functions.
  • Using threads for performing operations in parallel.
  • Structuring code into modules and packages.
  • Building a Jython library using classes.

This article assumes that you have a working knowledge of the basic features of Jython, and are therefore familiar with reading Jython code. The advanced features that are discussed here are covered in sufficient depth to make the topic understandable, but for full details of Jython syntax and semantics, see the references listed in the Resources section.

Managing wsadmin attribute data using lists and dictionaries

Using lists

Jython provides a number of built-in data types. In Jython, a variable's data type is not statically declared; instead, it is dynamically assigned at run time. In addition to numeric types, Jython also has data types for sequences and dictionaries.

A sequence is a set of items that is ordered and indexed. There are three types of sequences: strings, tuples, and lists. Strings and tuples are immutable, while lists are mutable. Strings are delimited by single, double, or triple quotation marks, tuples by round brackets, and lists by square brackets. A common notation enables you to operate on sequence slices and indexes, and there are several methods that operate on strings and lists.

Most wsadmin Jython script writers tend to only use numeric and string types, but lists, tuples, and dictionaries are especially useful for holding data returned by Admin* APIs in first-class Jython data structures.

To see why you would want to hold data in a more Jython-aware format, look at the data returned by the wsadmin AdminConfig.show() API in Listing 1, taken from an interactive wsadmin session on Windows XP. Superficially, this looks like a Jython list, because it has square brackets surrounding each element in the returned data. On closer examination, you can see that the elements are separated by newline characters ("\r\n") and the entire result is surrounded by single quotation marks. Thus, wsadmin is actually returning a Jython string (indicated by the arrow labelled 1). If your code iterates through the returned data hoping to get each separate element, it will instead get each character of the string (indicated by the arrow labelled 2).


Listing 1. AdminConfig APIs return strings not lists
                
wsadmin>servid = AdminConfig.getid("/Server:server1/")

wsadmin>jvmid = AdminConfig.list("JavaVirtualMachine", servid)

wsadmin>jvmatts = AdminConfig.show(jvmid)

wsadmin>jvmatts
'[bootClasspath []]\r\n[classpath []]\r\n[debugArgs "-Djava.compiler=NONE -Xdebu
g -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=7777"]\r\n[
debugMode false]\r\n[disableJIT false]\r\n[genericJvmArguments []]\r\n[hprofArgu
ments []]\r\n[initialHeapSize 0]\r\n[internalClassAccessMode ALLOW]\r\n[maximumH
eapSize 0]\r\n[runHProf false]\r\n[systemProperties []]\r\n[verboseModeClass fal
se]\r\n[verboseModeGarbageCollection false]\r\n[verboseModeJNI false]'
wsadmin>type(jvmatts)
<jclass org.python.core.PyString at 238161458>            <--------- 1

wsadmin>for item in jvmatts:
wsadmin>  print item
wsadmin>
[                                                         <--------- 2
b
o
o
t
.
.
.

To get the elements, you first have to remove the line separators and convert the string to a Jython list. You use the splitlines() function to turn the results into a list, as in Listing 2.


Listing 2. Getting AdminConfig APIs to return Jython lists
                

wsadmin>jvmatts = AdminConfig.show(jvmid).splitlines()

wsadmin>type(jvmatts)
<jclass org.python.core.PyList at 381687488>            <--------- 1

wsadmin>for item in jvmatts:
wsadmin>  print item
wsadmin>
[bootClasspath []]                                      <--------- 2
[classpath []]
[debugArgs "-Djava.compiler=NONE -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket
,server=y,suspend=n,address=7777"]
. . .

wsadmin>type(jvmatts[2])
*lt;jclass org.python.core.PyString at 238161458>       <--------- 3

          

Now, jvmatts is a bona fide Jython list (indicated by the arrow labelled 1) and you can use iteration (indicated by the arrow labelled 2) as well as slicing, indexing, and the Jython list functions to manipulate it.

However, appearances can be deceptive: these items are actually strings (indicated by the arrow labelled 3). You managed to create a Jython list from the line-separated string, but each individual element is still a Jython string. More processing is necessary to get each individual element treated as a list, but it is better to get it handled as a dictionary.

Using dictionaries

Searching for a particular attribute name in a list where the entries are name-space-value strings, or are sub-lists of name-value elements, is possible but awkward to code. Jython provides the dictionary data type that is the natural solution for holding wsadmin attribute data.

A dictionary is an unordered collection of values indexed by keys. It is akin to a named array in Jacl. Syntactically, a dictionary is delimited by curly brackets with the entries separated by commas and each entry comprising a key-value pair separated by colons. The key can be any immutable value, such as a string or number.

Listing 3 continues the previous example to transform the returned list into a dictionary. This enables you to easily access any required attribute by its name.


Listing 3. Getting AdminConfig APIs to return Jython dictionaries
                

wsadmin>aDict = {}

wsadmin>servid = AdminConfig.getid("/Server:server1/")

wsadmin>jvmid = AdminConfig.list("JavaVirtualMachine", servid)

wsadmin>for item in AdminConfig.show(jvmid).splitlines():
wsadmin>  item = item[1:len(item) - 1]
wsadmin>  spacePos = item.find(' ')
wsadmin>  aDict[item[0:spacePos]] = item[spacePos + 1:]
wsadmin>
wsadmin>aDict
{'internalClassAccessMode': 'ALLOW', 'debugArgs': '"-Djava.compiler=NONE -Xdebug
 -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=7777"', 'cla
sspath': '[]', 'initialHeapSize': '256', 'runHProf': 'false', 'genericJvmArgumen
ts': '[]]', 'hprofArguments': '[]]', 'bootClasspath': '[]', 'verboseModeJNI': 'f
alse', 'systemProperties': '[myname(cells/wasCell/nodes/appServNode/servers/engi
ne1|server.xml#Property_1191570369046)]', 'maximumHeapSize': '512', 'disableJIT'
: 'false', 'verboseModeGarbageCollection': 'true', 'verboseModeClass': 'false',
'debugMode': 'false'}

wsadmin>aDict["maximumHeapSize"]
'512'
wsadmin>aDict["verboseModeGarbageCollection"]
'true'

          

You are now able to access any wsadmin attribute by its name, though its value is still a string. To perform arithmetic operations, it is necessary to convert it using an appropriate function (for example, int()). You can use the AdminConfig.attributes() API to find the wsadmin data type of a particular attribute and, therefore, convert it to an appropriate Jython data type.

Using list comprehension and functional programming

Using list comprehension

List comprehension enables you to evaluate an expression on each member of an input list, creating an output list as its result. For each list member, a filter can be applied to let you choose whether or not the expression should be evaluated. This can be used in situations where wsadmin requires a list as input.

AdminTask modify operations, such as modifyServerPort(), enable you to pass a Jython list of strings that determine the attributes to be updated. Suppose you have a Jython dictionary portsDict that has wsadmin attribute names as keys. You could use list comprehension to process the dictionary and generate the list that wsadmin requires, as shown in Listing 4.

(This and all listings in this article omit an AdminConfig.save() statement. You will need to add this if you want to make the modifications permanent.)


Listing 4. Using list comprehension to generate parameters for AdminTask
                
portsDict = {}
portsDict["nodeName"] = "myNode"
portsDict["endPointName"] = "SIB_ENDPOINT_ADDRESS"
portsDict["host"] = "*"
portsDict["port"] = "2222"
AdminTask.modifyServerPort("server1",
             ["-%s %s" % (key, value) for key, value in portsDict.items()])

(If you run this example inside an interactive wsadmin session, you will need to type the last two lines on a single line.)

The list comprehension syntax states that, for each key-value pair in the portsDict dictionary, return a list element of the form "-nodeName myNode" (this is achieved by the string format statement "-%s %s" % (key, value)). The result of the list comprehension is a Jython list in the format that AdminTask requires.

Using a functional programming style

Although not designed as a functional programming language, Jython provides a number of constructs that enable a functional programming style to be used. Functional programming places an emphasis on programming using expressions, recursion, and list processing, and especially on using functions as first-class objects (that can be passed as parameters in function calls, or assigned to variables). Elimination, or, more realistically, minimisation, of the use of statements, variables, and name rebinding, is seen as key in functional programming.

You have just seen how Jython's list comprehension applies an expression or function to every element of a list, producing another list. Jython also provides the following functions (and others not discussed here) that enable a functional programming style to be used:

  • Anonymous functions using lambda forms. Lambda calculus underpins functional programming. Jython provides the lambda construct to define a function as an expression without using either variables or multiple statements. A simple example that calculates leap years could be written as follows:

    isLeapYear = lambda year: not (year % 400 and (year % 4 or not year % 100))

    You call this function like any other, for example isLeapYear(2000).

  • map() iterates over a sequence applying a function to each member. It can also operate on a set of sequences applying the function to to a tuple formed from each member. The following example calculates which years are leap years (range(2000,2010) producing a list of integers from 2000 to 2009):

    map(lambda x: isLeapYear(x), range(2000,2010))

  • filter() also iterates over a sequence applying a function to each member, filtering out members for which the function returns a false value.

Listing 5 demonstrates how a functional programming style can be used to update the initial and maximum heap sizes of every server object, returning a list of the server names that have been updated.


Listing 5. Using a functional programming style to update heap size settings
                
import re
    
map(lambda x:
    AdminConfig.modify(x, [["initialHeapSize", 64],
                           ["maximumHeapSize", 128]]) == ""
       and re.sub(".*/servers/(.*)\|.*", r"\1", x),
    filter(lambda y:
           y.find("nodeagent") == -1 and y.find("dmgr") == -1,
           AdminConfig.list("JavaVirtualMachine").splitlines()))

(If you run this inside an interactive wsadmin session, you will need to type the entire map() statement on a single line.)

In this example, the filter() function builds a sequence of JavaVirtualMachine objects, filtering out the deployment manager and node agents. This sequence is passed to the map() function, which applies the AdminConfig.modify() API to every element of the list. If the modification is successful, indicated by AdminConfig.modify() returning an empty string, then a Jython regular expression is used to extract the server name from the full object name. This string value becomes the returned element in the list, which therefore contains the names of the updated servers.

This could clearly be rewritten as a for loop with an if statement and doing so might result in greater clarity in this particular example. Adherents of functional programming might prefer this map() approach, or they might prefer to use list comprehension instead. Jython offers a choice of styles.

Providing flexible function interfaces

Jython differentiates between functions and methods: a function is callable outside of a class whereas a method belongs to a class. This section covers functions.

Compared with other programming languages, Jython has some significant differences in function definitions:

Anonymous strings

Strings can be delimited by single quotes, double quotes, or triple quotes (three successive single quotes). Triple quotes enable you to have a string covering multiple lines. A string not assigned to a variable is an anonymous string and is treated as a comment. Placing an anonymous string after a class, method, or function definition changes the anonymous string into a doc string. An example is shown in Listing 8.

  • There is considerable flexibility in parameter passing. You will see this in the next example.
  • More than one value can be returned as the result of the function. Jython returns a tuple in this situation.
  • By placing a doc string immediately after the function definition, you enable users of this function to retrieve documentation about it using <function>.__doc__.
  • Functions can have attributes that can be assigned inside or outside of the function itself.

The flexibility of parameter passing enables you to provide a much more friendly and extensible interface when defining functions. (The same applies to methods in classes.) A function's signature comprises positional parameters with optional default values, variable parameters that are passed to the function as a tuple, and name=value pairs that are passed as a dictionary. In the function's signature, you indicate the receiving tuple and dictionary by prefixing the argument names with a single asterisk and double asterisk, respectively. Listing 6 shows how this works:


Listing 6. Using the flexibility of function definitions
                

def myFunction(p1, p2="def", *p3, **p4):
  print vars()

myFunction("abc")
    ---> {'p4': {}, 'p1': 'abc', 'p2': 'def', 'p3': ()}

myFunction(p2="xyz", p1="uvw")
    ---> {'p4': {}, 'p1': 'uvw', 'p2': 'xyz', 'p3': ()}

myFunction("abc", "ghi", "jkl", "mno")
    ---> {'p4': {}, 'p1': 'abc', 'p2': 'ghi', 'p3': ('jkl', 'mno')}

myFunction("a", "b", "c", "d", id1="e", id2="f")
    ---> {'p4': {'id2': 'f', 'id1': 'e'}, 'p1': 'a', 'p2': 'b', 'p3': ('c', 'd')}
    
          

In the function body, vars() prints a dictionary of variables known in the current scope. The first example shows that the positional parameter p2, which was not passed on the call, has been given a default value. The second example shows that by using the name of the parameter in the call, the caller can pass parameters in any convenient order. This immediately makes the code more self-documenting and comprehensible. The third example demonstrates how additional parameters not explicitly named in the function signature are collected together and passed to the function as a tuple. The final example shows how additional name=value parameters are collected together in a dictionary.

This flexibility of parameter definition and parameter passing is readily exploitable in wsadmin scripting. Most script writers accustomed to Jacl produce function definitions that have fixed parameters. If the requirement changes and a new parameter is necessary, the script writer has to change the function signature. With Jython's flexible parameters, this is no longer necessary, which you will see in a later example.

Leveraging dictionaries and flexible functions

You will now use functions, dictionaries and the functional programming style to write a function that can update any simple wsadmin attribute. Here, "simple" means an attribute whose value is not a collection or a reference (for an explanation of wsadmin attribute types, see the help information provided via AdminConfig.help("attributes")). Listings 7 through 10 contain the code for achieving this generic update capability. Rather than cutting and pasting these listings into an interactive wsadmin session, you can download the file setvalues.py. You will need to edit the server and data source names to correspond to items in your cell. Place the file in the directory from which you launched wsadmin and then run execfile("setvalues.py").

Start by building a big dictionary allAttsDict of all of the wsadmin simple attributes (Listing 7):


Listing 7. Building the attributes dictionary
                
from __future__ import nested_scopes
    
def convertToDict(aNestedList):
  aDict = {}
  for aList in aNestedList:
    for item in aList:
      aDict[item[0]] = item[1]
  return aDict
      
attsToListFunc = lambda type:
  map(lambda x:
    (x[0:x.index(" ")] + "_" + type, x[x.index(" ") + 1:]),
    filter(lambda x: x.endswith("*") == 0 and x.endswith("@") == 0,
      AdminConfig.attributes(type).splitlines()))

allAttsList = map(lambda x: attsToListFunc(x), AdminConfig.types().splitlines())

allAttsDict = convertToDict(allAttsList)

In this example:

  • attsToListFunc() is a function that operates on a wsadmin object type (such as a JavaVirtualMachine) to produce a Jython list comprising two-element tuples. The first element of each tuple is the name and object type of a simple attribute in the form <attribute>_<objectType> (for example, initialHeapSize_JavaVirtualMachine). The second element is the attribute's type (for example, int).

  • In the penultimate line, the map() function is used to call attsToListFunc() for each wsadmin object type. This builds a list where each member is a list of two-element tuples.

  • The final line uses the function convertToDict() to convert this list to a dictionary allAttsDict, whose key is <attribute>_<objectType> and whose value is the attribute's type.

Now, you will implement a function setValues() that will be capable of modifying the value of any wsadmin simple attribute. Its signature is shown in Listing 8:


Listing 8. Using an anonymous string
                          
def setValues(baseType, simpleName, qualifier=None, **setThese):
  ''' setValues() allows you to update simple attributes of wsadmin objects
      baseType   - the type of the object, e.g. "Server"
      simpleName - the object's name, e.g. "server1"
      qualifier  - the subtype of an object, e.g. "WebContainer"
      setThese   - name=value pairs of attributes to be modifed
                   Each name is in the format "attname_objname", where attname
                   is the name of an attribute that belongs to the wsadmin
                   object type objname, e.g. initialHeapSize_JavaVirtualMachine
                   
      Example:     setValues(baseType = "Server",
                             simpleName = "server1",
                             initialHeapSize_JavaVirtualMachine = 1024,
                             maxInMemorySessionCount_TuningParams = 200,
                             parallelStartEnabled_Server = "false")
      
  '''

The implementation of setValues() involves checking that each input parameter is present in the allAttsDict and then performing the update. To simplify the example, there is no validation and exception handling shown here. For a robust implementation, you would want to validate the types of values of the input variables (the allAttsDict stores their wsadmin types) and check that the AdminConfig calls return non-empty results, raising exceptions as appropriate. You would also want to enable the scope of the object that is being modified to be specified (that is, whether cell-, node-, server- or cluster-based).


Listing 9. Implementation of a function to update any simple wsadmin attribute
                
def setValues(baseType, simpleName, qualifier=None, **setThese):
  objid = AdminConfig.getid("/" + baseType + ":" + simpleName + "/")
  for attrUndType, value in setThese.items():
    undPos = attrUndType.find("_")
    if allAttsDict.has_key(attrUndType):
      attrName = attrUndType[:undPos]
      attrType = attrUndType[undPos+1:]
      attrTypeIdList = AdminConfig.list(attrType, objid).splitlines()
      if qualifier:
        for listItem in attrTypeIdList:
          if listItem.startswith(qualifier):
            attrTypeId = listItem
            break
      else:
        if len(attrTypeIdList) == 1:
          attrTypeId = attrTypeIdList[0]
      AdminConfig.modify(attrTypeId, [[attrName, value]])

AdminConfig.save()

If you have downloaded the scripts to try them in a test cell, you need to call AdminConfig.save() to persist the changes.

You can then use the function as shown in the next examples. In the first call to setValues(), you are setting three attributes of three different wsadmin object types that belong to the server /Server:server1/. In the second example, the qualifier parameter is used to target the changes purely to the WebContainer thread pool. In the third example, connection pool and data source attributes are updated for a particular data source. All this is done under one function and its user interface is friendly enough to enable this to be used interactively. All the caller needs to know are the names of the attributes and objects that are to be updated.


Listing 10. Using a function to update any simple wsadmin attribute
                
setValues("Server", "server1",
  initialHeapSize_JavaVirtualMachine = 1024,
  maxInMemorySessionCount_TuningParams = 200,
  parallelStartEnabled_Server = "false")

setValues("Server", "server1",
  description_ThreadPool="some description",
  minimumSize_ThreadPool=2,
  maximumSize_ThreadPool = 17,
  qualifier="WebContainer")
        
setValues("DataSource", "mysource",
  jndiName_DataSource = "jdbc/mysource",
  aged_Timeout = 20,
  maxConnections_ConnectionPool = 30,
  minConnections_ConnectionPool = 10)

Using threads for performing operations in parallel

Jython provides a threading package that you can use to perform administration tasks in parallel within a single script. You can start threads, wait for threads to complete, find information about them, and synchronise access to shared data structures. Listing 11 demonstrates how this could be used to start application servers in parallel.


Listing 11. Using threads to start application servers in parallel
                
import threading

aNode = raw_input("Enter node name: ")

def startAServer(server):
  print server, "is starting"
  AdminControl.startServer(server, aNode)
  print server, "has started"

for server in "server1", "server2":
  t = threading.Thread(target=startAServer, name=server, args=(server,))
  t.start()

(If you run this example on your own system you will need to alter the names of the servers being started.)

Although the example above is straightforward, thread programming in general needs care and attention, especially when updating data structures which are shared across multiple threads. If you write scripts that use threads, you need to ensure that you synchronise access to shared data correctly. You are more likely to use threading to parallelise administration tasks than you are to parallelise configuration tasks.

Structuring code into modules and packages

In Jython, a module is a source file suffixed .py (even for Jython) and a package is a collection of modules in a directory tree. Each directory must have an __init__.py file (usually empty); this identifies the directory as being a package. Thus, if you create a Jython source file C.py located in directory B which, in turn, is located in directory A, and you create empty __init__.py files in both A and B, then A and B are packages and C is a module.

You use modules by importing them. In the source file D.py you use the syntax import A.B.C to make C's namespace available, and you refer to a function someFunction() contained in C as A.B.C.someFunction(). Although verbose, this fully qualified naming convention avoids inadvertent rebinding of the same name that is present in two or more modules. This can happen with the alternative import mechanism from A.B import C. In this alternative import scheme, you do not need to use fully qualified names to access the contents of C, but it may have the side effect of rebinding an existing name from your own code or another module.

You can also use the keyword as to introduce a synonym to avoid lengthy typing. For example, if you use import A.B.C as E then C's namespace becomes available as E.

Using packages and modules helps to compartmentalise your code and make it more readable and, therefore, more maintainable. However, you might experience a difficulty in using the wsadmin Admin* APIs if you modularise your scripts. Listing 12 demonstrates this.

Suppose that you have created the modules shown below. Here, module abcd calls the listServers() function contained in module efgh, which uses AdminConfig.list() to print a list of servers.


Listing 12. Using modules with Admin* APIs - global doesn't work
                

    File abcd.py                     |  File efgh.py
    ------------                     |  ------------
                                     |
    import efgh                      |  def listServers():
    efgh.listServers()               |    print AdminConfig.list("Server")

          

The call to efgh.listServers() fails with a Jython NameError because of the way namespaces work in Jython. There are global and local namespaces, which apply for each source file. Variables within the scope of a function or method are local to that function unless explicitly flagged as being global. But even when flagged as global, it is only global within the confines of that source file. The Admin* objects are available in the source file invoked from the wsadmin command line or in the interactive session, but they are not automatically made available in source files that you import. Script writers familiar with Jacl might think that you can just include the line global AdminConfig within the listServers() function definition to solve this, but a Jython NameError still occurs.

There are two solutions to this issue:

  • Use execfile() to make the called source file have the same namespace as the calling source file:

    Listing 13. Using modules with Admin* APIs - using execfile rather than import
                            
              
        File abcd.py
        ------------
        
        execfile("efgh.py")
        listServers()
    
                

    Although this works, it largely removes the advantages of using packages and modules. It has effectively flattened the namespace, and if there are identical names in both files, there is potential for a name being wrongly bound.

  • A better solution is to provide a simple function setAdminRefs() in each imported source file to which you pass a tuple of the Admin* wsadmin objects. setAdminRefs() then sets them in the global address space of the called source file:

    Listing 14. Using modules with Admin* APIs - using a qualified name to reach AdminConfig
                            
              
        File abcd.py                     |  File efgh.py
        ------------                     |  ------------
                                         |
        import efgh                      |           
        efgh.setAdminRefs((AdminConfig,  |   def setAdminRefs(adminTuple):
                           AdminControl, |     global AdminConfig, AdminControl
                           AdminTask,    |     global AdminTask, AdminApp
                           AdminApp))    |     (AdminConfig, AdminControl,
                                         |      AdminTask, AdminApp) = adminTuple
                                         |
                                         |   def listServers():
        efgh.listServers()               |     print AdminConfig.list("Server")
    
                

    This works, provided a coding protocol is followed in which you call a source file's setAdminRefs() function immediately after you import the source file and before you use any of its other functions, classes, and methods. (The abcd.py and efgh.py file can be downloaded.) To run this example in an interactive wsadmin session, type execfile("abcd.py").

Building a Jython library using classes

For ad hoc or simple wsadmin scripts, you might not use Jython classes, but if you are building a script library, you may want to consider the advantages of using an object-oriented (OO) approach. In conjunction with packages and modules, classes can provide a well-structured, highly usable, and maintainable approach to scripting.

Jython classes are similar to Java classes, but there are some significant differences:

  • There can be more than one class per source file.
  • A class can inherit from multiple classes, including a single Java class.
  • Class-level and instance-level attributes (members) and instance-level methods are available, but class-level (static) methods do not exist. Because you can have functions outside of classes, to some extent functions play the part of class-level methods.
  • Privacy is by naming convention rather than enforced by keyword. A method or attribute that is meant to be private should be prefixed with a single underscore. This only discourages direct access externally to the class but does not prevent access. To radically discourage access, you can use two underscores, but even then, a determined programmer can access the variable. Privacy in Jython is behavioural but not enforced.
  • Jython methods cannot be overloaded.

Using an object-oriented approach can make your script more readable and, therefore, comprehensible. Listing 15 shows how you can use classes to provide a simple way of updating the JavaVirtualMachine definition.


Listing 15. Defining a class for displaying and updating JavaVirtualMachine attributes
                

def setAdminRefs(adminTuple):
  global AdminConfig, AdminControl, AdminTask, AdminApp
  (AdminConfig, AdminControl, AdminTask, AdminApp) = adminTuple
    
def toDict(aString):
  aDict = {}
  for item in aString.splitlines():
    item = item[1:len(item) - 1]
    spacePos = item.find(' ')
    aDict[item[0:spacePos]] = item[spacePos + 1:]
  return aDict
  
class JVM:

  attsString = "initialHeapSize maximumHeapSize debugMode"
  
  def __init__(self, server = "server1"):
    serverId = AdminConfig.getid("/Server:" + server + "/")
    jvmIdsList = AdminConfig.list("JavaVirtualMachine", serverId).splitlines()
    if len(jvmIdsList) != 1:
      raise RuntimeException, "Expected one JVM ID, got " + len(jvmIdsList)
    self.jvmString = jvmIdsList[0]

  def getValuesAsDict(self):
    return toDict(AdminConfig.show(self.jvmString, JVM.attsString))

  def setValuesFromDict(self, myDict):
    attsList = []
    for att in myDict.keys():
      attsList.append([att, myDict[att]])
    AdminConfig.modify(self.jvmString, attsList)
    
          

This file (jvmclass.py, available to download) contains two utility functions and a class. The first utility function setAdminRefs() has already been discussed. The second function toDict() converts a string of attribute name-value pairs (as returned by the AdminConfig.show() API), to a Jython dictionary. The JVM class has a constructor and two instance methods. In a method definition, you need to provide a dummy first parameter, conventionally known as self. The constructor is the __init__() method, which uses the server's name to find the identity of the JavaVirtualMachine object which it stores as an instance attribute. The class defines a class-level attribute attsString of attribute names that the user of this class is allowed to manipulate. These are obtained via the getValuesAsDict() method, which stores them as a Jython dictionary. The user can then change one or more of these attributes in the dictionary and pass it back for update via the setValuesFromDict() method.

The user of this class writes scripts as shown in Listing 16. To run this in an interactive wsadmin session, you need to place the jvmclass.py file in your working directory and cut and paste the statements below.


Listing 16. Using the JVM class to update JavaVirtualMachine attributes
                

import jvmclass
    
jvmclass.setAdminRefs((AdminConfig, AdminControl, AdminTask, AdminApp))

jvmid = jvmclass.JVM("server1")
myDict = jvmid.getValuesAsDict()
myDict["initialHeapSize"] = 512
myDict["debugMode"] = "true"

jvmid.setValuesFromDict(myDict)

          

If you now need to extend the idea above to enable a similar usage style from other classes, then you could copy jvmclass.py for each new wsadmin object. However, recognising the commonality that exists across wsadmin objects, you can do much better than that: you can move much of the logic to a base class and have the code for the individual wsadmin objects extend from that base class. Listing 17 demonstrates this.


Listing 17. Defining a base class for displaying and updating attributes of any wsadmin object type
                

def setAdminRefs(adminTuple):
  global AdminConfig, AdminControl, AdminTask, AdminApp
  (AdminConfig, AdminControl, AdminTask, AdminApp) = adminTuple
    
def toDict(aString):
  aDict = {}
  for item in aString.splitlines():
    item = item[1:len(item) - 1]
    spacePos = item.find(' ')
    aDict[item[0:spacePos]] = item[spacePos + 1:]
  return aDict
  
class Base:
  def __init__(self, objName):
    parentId = AdminConfig.getid("/" + self.parentType + ":" + objName + "/")
    objIdsList = AdminConfig.list(self.thisType, parentId).splitlines()
    if len(objIdsList) != 1:
      raise RuntimeException, "Expected one ID, got " + len(objIdsList)
    self.objIdString = objIdsList[0]

  def getValuesAsDict(self):
    return toDict(AdminConfig.show(self.objIdString, self.attsString))

  def setValuesFromDict(self, myDict):
    attsList = []
    for att in myDict.keys():
      attsList.append([att, myDict[att]])
    AdminConfig.modify(self.objIdString, attsList)

class JVM(Base):
  attsString = "initialHeapSize maximumHeapSize debugMode"
  parentType = "Server"
  thisType = "JavaVirtualMachine"
  
class Trace(Base):
  attsString = "startupTraceSpecification enable traceOutputType memoryBufferSize"
  parentType = "Server"
  thisType = "TraceService"

          

In this new version (base.py, available to download), you have moved getValuesAsDict() and setValuesFromDict() to a class called Base, and have introduced two new classes JVM and Trace. These classes each provide three class-level attributes: attsString (as in the earlier example), parentType, and thisType. parentType defines the ultimate parent of a wsadmin object (for example, Server) and thisType defines the wsadmin object type for this class.

A calling script that updates JavaVirtualMachine and TraceService settings might then look like Listing 18. To run this in an interactive wsadmin session, you need to place the base.py file in your working directory and cut and paste the statements below.


Listing 18. Using the JVM and Trace class to update JavaVirtualMachine and TraceService attributes
                

import base

base.setAdminRefs((AdminConfig, AdminControl, AdminTask, AdminApp))

jvmId = base.JVM("server1")
myDict = jvmId.getValuesAsDict()
myDict["initialHeapSize"] = 512
myDict["debugMode"] = "true"

jvmId.setValuesFromDict(myDict)

traceId = base.Trace("server1")
traceDict = traceId.getValuesAsDict()
traceDict["memoryBufferSize"] = 10
traceDict["startupTraceSpecification"] = "*=info;com.mycom.myproj=all"
traceId.setValuesFromDict(traceDict)
    
          

This code could be further modularised by placing the JVM and Trace classes in their own files and then importing them. As with the earlier setValues() code, validation and error checking logic has been omitted to enhance clarity. Even when this is added, the overall code is more readable, maintainable, and intuitive than using monolithic, unstructured wsadmin scripting that is typically written today, and therefore should be easier to debug, maintain, and enhance.

Conclusion

This article has shown that there are a number of features in the Jython language that can be leveraged to build scripts that are more reliable and understandable than might otherwise be the case:

  • Turning the output of wsadmin Admin* APIs into Jython data types such as lists and dictionaries can make it much easier to manipulate and organise data. This is especially true of dictionaries, which are a natural way of holding wsadmin attribute names and values.
  • Although many script writers will steer clear of functional programming, others may prefer the programming style that it offers. List comprehension is useful for producing the attribute-value lists that wsadmin Admin* APIs require.
  • Jython's function parameter syntax means that you can provide functions whose signatures do not need to constantly change whenever a new parameter is needed.
  • By exploiting dictionaries and flexible functions, you can provide a simple API that can update any wsadmin object with a minimal amount of typing.
  • You can perform administration operations in parallel from one script by using threads.
  • By dividing code into modules and packages, you can provide a well-structured function library that is easy to use and maintain.
  • By using classes and inheritance in conjunction with modules and packages you can build well-crafted wsadmin libraries.

Finally, you also have full access to Java from Jython: but you may not need it. Before dropping into Java from Jython, you should consider the maintenance aspect of your scripts. Most script writers are more likely to be Jacl- or shell script-aware than they are Java-aware, and coding parts of your scripts in Java may lead to maintenance or skills problems.

If you do require Java, then you can access Java libraries using one of the import mechanisms. For example, you can access the Java string handling functions via from java.lang import String. This would then enable you to use Java string handling rather than Jython string handling.

Before you use Java APIs with which you may already be familiar, look to see if there is already something available in Jython, which will often be the case. Look in particular at the functions available in the os, os.path, and sys modules, and use these in preference to equivalents in Java.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值