Use VBScript to cheat at poker with JMX!
By jeanfrancoisdenise on 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.
Intialisation of the WS-Management access
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.
Subscription to the MBean notifications
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.
Retrieving the Eventing context
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.
Retrieving the 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
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.
Extracting the Notification content and tracking the cheat
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
Ejecting the player
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.