Friday May 23, 2008 This year at JavaOne Eamonn and I presented during our technocal Session, where we stand with respect to JMX. Eamonn covered JMX in general and JMX 2.0, while I covered the Web Services Connector for JMX. At the end of the talk, we performed a demo named “CSPoker, JMX Technology, Java™ VisualVM and WinRM at the JavaOne Tournament's Final Table”. We linked together a set of JMX related technologies to offer, in an original Online Poker Web Site use case, an interesting setup highlighting the power of JMX (here's a PDF file of the demo architecture) .
At the end of the talk, some people asked me some questions about the WS-Management access to JMX we had just demonstrated. More details on the WS-Management to JMX interoperability can be found in this article.
Here, I am providing some details on the actual script used during the demo. This script automates the Poker Engine monitoring and management tasks. It first registers to listen for JMX Notifications exposed as WS-Eventing notifications. Each time a “Poker table joined” notification is received, it checks that the IP address from which the player joined is not already known to the system. If an IP address is already known, it means that another user already joined from the same host.... which is very bad if you want to avoid the same player using multiple identities....
When such a “bad user” is detected, the script ejects all the players that joined from that machine.
dim wsmanObj
set wsmanObj = CreateObject("WSMAN.Automation")
dim objConnectionOptions
set objConnectionOptions = wsmanObj.CreateConnectionOptions
dim iFlags
iFlags = wsmanObj.SessionFlagUseNoAuthentication
dim session
set session = wsmanObj.CreateSession("http://localhost:8080/admin", iFlags)
At this point, you have a WS-Management proxy (the session object), that allows you to interact with a JMX Agent attached to the Web Services Connector.
The MBean we want to subscribe to is named : cspoker:type=CSPokerControl
Because the WS-Management session doesn't offer a nice API for subscriptions, we need to construct the subscription invocation request using the “all purpose” invoke method :
Dim reply
reply = session.invoke("http://schemas.xmlsoap.org/ws/2004/08/eventing/Subscribe", _
"http://jsr262.dev.java.net/DynamicMBeanResource?ObjectName=cspoker:type=CSPokerControl", _
"<wse:Subscribe xmlns:wse='http://schemas.xmlsoap.org/ws/2004/08/eventing'>"_
& "<wse:Delivery Mode='http://schemas.dmtf.org/wbem/wsman/1/wsman/Pull'/></wse:Subscribe>")
At this point we have sent a WS-Eventing subscription request (in PULL mode, meaning that the client will send a request to pull notifications from the server) and received a response. The response contains the WS-Eventing context to be used to retrieve notifications.
To get the eventing context from the response, we need to make use of the VBScript DOM library : Microsoft.XMLDOM
Warning : Because namespaces are not handled well in this DOM library, we are trying to discover the Namespace prefix based on our knowledge of the way they are declared (ns<i>). I am not a VBScript expert and would be very interested to know if there is an equivalent to getElementsByTagNameNS using VBScript???
Function findElement(elem, tagElem)
' wscript.echo "Finding Element " & tagElem
for i = 0 to 15
set findElement = elem.getElementsByTagName("ns"& i &":" & tagElem)
If findElement.Length > 0 Then Exit For
next
End Function
Dim objXMLDoc
set objXMLDoc = CreateObject("Microsoft.XMLDOM")
objXMLDoc.async = False
objXMLDoc.loadXML(reply)
' Get the Eventing context to use in the next WS-Eventing Pull request
Dim nodeList
set nodeList = findElement(objXMLDoc.documentElement, "EnumerationContext")
dim context
context = nodeList(0).text
wscript.echo "Subscription Enumeration Context " & context
At this point, the context variable contains the WS-Eventing context that identifies our subscription. This context is to be used to retrieve notifications.
This is done by sending a Pull request to the server and providing it the context. The pull request blocks until some Notifications are emitted or the timeout (1 minute by default) is reached. As for subscribe requests, no Pull API is offered. We need to use the general purpose invoke operation. We have written a pull function that handles pull request timeouts properly. When the timeout occurs, instead of exiting the script, the error is trapped and the script continues. Because we expect multiple notifications, the Notification pulling is done in a loop.
Function pull(session, context) On Error Resume Next Dim pullXml pullXml = "<wsen:Pull xmlns:wsen='http://schemas.xmlsoap.org/ws/2004/09/enumeration'><wsen:EnumerationContext>" & context & "</wsen:EnumerationContext><wsen:MaxTime>PT1M0.000S</wsen:MaxTime><wsen:MaxElements>1000</wsen:MaxElements></wsen:Pull>" pull = session.invoke("http://schemas.xmlsoap.org/ws/2004/09/enumeration/Pull", "http://jsr262.dev.java.net/MBeanNotificationSubscriptionManager", pullXml) On Error Goto 0 End Function ' This piece of script is called in an infinite loop
Dim notifs
notifs = pull(session, context)
At this point, the notifs variable contains (or does not contain, if the timeout occurred) the JMX notifications in an XML format.
We only do this extraction if the list of notifications is not null. The VBScript DOM library is again used to parse the notifications. Because a pull request can return multiple notifications, we must iterate on all TargetedNotification elements contained in the notification list. A TargetedNotification is an XML representation of a JMX Notification defined by the JSR 262 (JSR in which the Web Services Connector is defined).
In our case, the TargetedNotification/Message XML Element contains the Player name. The TargetedNotification/UserData XML Element contains the IP Address as a string (eg:<jmx:String>192.168.0.1</jmx:String>)
To keep the IP <==> Player relationship, we use a Map data structure and array. In VBScript, we use a dictionnary (scripting.dictionary Libray)
' Create a Map dim dict set dict = CreateObject("scripting.dictionary") Dim ipArray(10) Dim arrayIndex arrayIndex = 0
if Not(isNull(notifs)) then
set objXMLNotif = CreateObject("Microsoft.XMLDOM")
objXMLNotif.async = False
objXMLNotif.loadXML(notifs)
set NodeListNotifs = findElement(objXMLNotif.documentElement, "TargetedNotification")
WScript.echo "Pull returned " & NodeListNotifs.length & " notifications"
' Now that we get the list of notifications, extract the content.
Dim i
Dim EventType
Dim msgObj
' Loop over the received Notifications
For i = 0 To NodeListNotifs.length - 1
Dim listMsg
set listMsg = findElement(NodeListNotifs(i), "Message")
Dim eventMessage
eventMessage = listMsg(0).text
Dim listUserData
set listUserData = findElement(NodeListNotifs(i2), "String")
' We have an IP address
if(listUserData.length > 0) then
Dim ipAddress
ipAddress = listUserData(0).text
wscript.echo eventMessage & " Joined From " & ipAddress
if dict.exists(ipAddress) then
Dim prev
prev = dict.item(ipAddress)
if Not StrComp(prev,eventMessage) Eqv 0 Then
dim users
users = Array(eventMessage, prev)
For Each user In users
wscript.echo user & " already connected from same host [" & ipAddress &"], will eject him." '"Type return."
wscript.echo "Ejecting player " & user
ejectPlayer session, user
wscript.echo "Player " & user & " ejected"
Next
end if
else
dict.add ipAddress, eventMessage
end if
else
wscript.echo eventMessage
end if
Next
The function ejectPlayer calls the ejectPlayer MBean operation by making use of the general purpose invoke operation.
Function ejectPlayer(session, playerName)
dim pokerURI
pokerURI = "http://jsr262.dev.java.net/DynamicMBeanResource?ObjectName=cspoker:type=CSPokerControl"
dim xmlInvoke
xmlInvoke = "<jmx:ManagedResourceOperation name=" & Chr(34) & "ejectPlayer" & Chr(34) & " xmlns:jmx=" & Chr(34) & "http://jsr262.dev.java.net/jmxconnector" & Chr(34) & "><jmx:Input><jmx:Param><jmx:String>" & playerName & "</jmx:String></jmx:Param></jmx:Input></jmx:ManagedResourceOperation>"
session.invoke "http://jsr262.dev.java.net/DynamicMBeanResource/Invoke", pokerURI, xmlInvoke
End Function
Hope that these VBScript extracts helped you understand better how you can use this path to interoperate with JMX.
Thanks.
Jean-François Denise
jfd@sun.com
A. Sundararajan
Alan Bateman
Daniel Fuchs
Éamonn McManus
Joël Féraud
Mandy Chung
Luis-Miguel Alventosa