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.
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:
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).
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) |
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.
wsadmin>jvmatts = AdminConfig.show(jvmid).splitlines() wsadmin>type(jvmatts) |
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.
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.)
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:
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.
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:
.__doc__
.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:
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):
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
(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
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:
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).
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]]) |
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.
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.
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.
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:
execfile()
to make the called source file have the same namespace as the calling source file: 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.
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: 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:
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.
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.
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.
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.
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:
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.Admin*
APIs require.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.