ISIM 6 API Access from a remote machine

This question appears on many sites and blogs in separate parts, but I’ve not seen a complete description yet of the whole setup. Either way – this post documents my working example of getting the ISIM API working from a remote machine.

If you’re using IDI then the setup in terms of the classpath is the same apart from needing to put the jars either in your TDI installation directory JRE folder (under 3rdparty) or as specified by com.ibm.di.loader.userjars in your solution.properties file. The code will obviously need to be re-written in Javascript, but largely that’s just a matter of specifying methods with the full class name prefixed with “Packages.”.

I should also say that while the ISIM Web Services API is the initial client of choice it’s still a little limited so if you want to do more, then running a full remote API can be necessary. And of course if you need full beans functionality then you need to run inside the ISIM server itself to get the full API.

Anyway – back to Java an ISIM API on a remote machine.

Setup steps;

  • Download the IBM JDK for eclipse for your environment from here
  • Build a client directory – lets say that’s called C:\ISIM6_Client
  • Copy sas.client.props from the ISIM server. Use the one under <WAS_HOME> /profiles/AppSrv01/properties. Put the file into C:\ISIM6_Client\etc
  • Copy ssl.client.props from the ISIM server. Use the one under <WAS_HOME> /profiles/AppSrv01/properties. Put the file into C:\ISIM6_Client\etc
    • Edit this file and change the “user.root” at the top of the file to “C:\ISIM6_Client”
  • Copy (or create) key.p12 and trust.p12 in C:\ISIM6_Client\etc. You can copy these files from the ISIM server under <WAS_HOME>/profiles/AppSrv01/etc
    • Putting these files into C:\ISIM_Client\etc and updating ssl.client.props means that the ssl.client.props properties “com.ibm.ssl.keyStore” and “com.ibm.ssl.trustStore” will refer to them correctly.
    • These will contain the certificate of your ISIM server. If you point to a new ISIM server make sure “com.ibm.ssl.enableSignerExchangePrompt=gui” is set in ssl.client.props and then you will get a certificate import prompt that you can just accept to add the certificate for the new server to your trust store.
  • Either copy orb.properties from <WAS_HOME>/java/jre/lib/orb.properties into <JAVA_HOME>/lib or create one with the following contents;
# IBM JDK properties
org.omg.CORBA.ORBClass=com.ibm.CORBA.iiop.ORB
org.omg.CORBA.ORBSingletonClass=com.ibm.rmi.corba.ORBSingleton
javax.rmi.CORBA.StubClass=com.ibm.rmi.javax.rmi.CORBA.StubDelegateImpl
javax.rmi.CORBA.PortableRemoteObjectClass=com.ibm.rmi.javax.rmi.PortableRemoteObject
javax.rmi.CORBA.UtilClass=com.ibm.ws.orb.WSUtilDelegateImpl

 # WS Plugins
com.ibm.CORBA.ORBPluginClass.com.ibm.ws.wlm.client.WLMClient
com.ibm.CORBA.ORBPluginClass.com.ibm.ws.orbimpl.transport.WSTransport
com.ibm.CORBA.ORBPluginClass.com.ibm.ws.orbimpl.WSORBPropertyManager
com.ibm.CORBA.ORBPluginClass.com.ibm.ISecurityUtilityImpl.SecurityPropertyManager
com.ibm.CORBA.ORBPluginClass.com.ibm.ws.orb.WSSubcontractInitImpl

 # WS Interceptors
org.omg.PortableInterceptor.ORBInitializerClass.com.ibm.ws.Transaction.JTS.TxInterceptorInitializer
org.omg.PortableInterceptor.ORBInitializerClass.com.ibm.ejs.ras.RasContextSupport
org.omg.PortableInterceptor.ORBInitializerClass.com.ibm.ISecurityLocalObjectBaseL13Impl.ClientRIWrapper
org.omg.PortableInterceptor.ORBInitializerClass.com.ibm.ws.activity.remote.cos.ActivityServiceClientInterceptor
org.omg.PortableInterceptor.ORBInitializerClass.com.ibm.ISecurityLocalObjectBaseL13Impl.CSIClientRI
org.omg.PortableInterceptor.ORBInitializerClass.com.ibm.debug.olt.ivbtrjrt.OLT_RI
org.omg.PortableInterceptor.ORBInitializerClass.com.ibm.ws.wlm.client.WLMClientInitializer

 # WS ORB &amp; Plugins properties
com.ibm.ws.orb.transport.ConnectionInterceptorName=com.ibm.ISecurityLocalObjectBaseL13Impl.SecurityConnectionInterceptor
com.ibm.ws.orb.transport.WSSSLClientSocketFactoryName=com.ibm.ws.security.orbssl.WSSSLClientSocketFactoryImpl
com.ibm.CORBA.enableLocateRequest=true
com.ibm.CORBA.ORBCharEncoding=UTF8
com.ibm.CORBA.ForceTunnel=never
com.ibm.CORBA.TransportMode=Pluggable
  • Create the JAAS login file in C:\ISIM6_Client\etc\jaas_login_was.conf that contains the following line for ISIM 6;
WSLogin {
    com.ibm.ws.security.common.auth.module.proxy.WSLoginModuleProxy required delegate=com.ibm.ws.security.common.auth.module.WSLoginModuleImpl;
};
  • Finally, we need the jars that will make the client. I’m using Netbeans so I just create a new Library Tools->Libraries. Click the “New Library” button and add the following jars.
    • Note that tmsMessages.jar is a jar of the tmsMessages.* files from the ISIM server.

  • Next, create a new project in Netbeans;
    • Right click the project name, chose properties. Click Libraries.
      • Add or select the IBM JDK as your Java Platform – you may need create a new platform in “Manage Platforms” that points to the IBM JDK you downloaded in the first step.
      • Under the Compile tab add the ISIM6_Client library created in the previous step
  • Now, add some code to your project. This example is all about the setup of the client so I’m not spending time on the client code in particular, but the example below should be sufficient to get you going. Note that this code is not intended to be production ready – it’s a stripped down example to show exactly what’s happening. Also, check where you need to enter your usernames and passwords below;
package isimapiclient;
import com.ibm.itim.apps.ApplicationException;
import com.ibm.itim.apps.InitialPlatformContext;
import com.ibm.itim.apps.PlatformContext;
import com.ibm.itim.apps.identity.PersonMO;
import com.ibm.itim.dataservices.model.DistinguishedName;
import com.ibm.itim.dataservices.model.domain.Person;
import java.rmi.RemoteException;
import java.util.Hashtable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginContext;
import com.ibm.websphere.security.auth.callback.WSCallbackHandlerImpl;
import javax.security.auth.login.LoginException;

 
 public class IsimApiClient {

     public static void main(String[] args) {

         try {
            IsimApiClient client = new IsimApiClient();
            PlatformContext itimPlatform = client.getPlatform();
            Subject subject = client.getSubject();

             System.out.println("loginContext: "+itimPlatform.toString() + " - " + subject);

             DistinguishedName empDN = new DistinguishedName("erglobalid=SOMEERGLOBALID,ou=0,ou=people,erglobalid=00000000000000000000,ou=ibm,dc=com");
            PersonMO employeeMO = new PersonMO(itimPlatform, subject, empDN);
            Person emp = employeeMO.getData();
            System.out.println("Data: " + emp.getProfileName());
            System.out.println("Name: " + emp.getName());

         } catch (RemoteException ex) {
            Logger.getLogger(IsimApiClient.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ApplicationException ex) {
            Logger.getLogger(IsimApiClient.class.getName()).log(Level.SEVERE, null, ex);
        } catch (LoginException ex) {
            Logger.getLogger(IsimApiClient.class.getName()).log(Level.SEVERE, null, ex);
        }

     }

 
     public PlatformContext getPlatform() {

         String hostname = "YOUR HOSTNAME";
        String port = "2809";    (Check BOOTSTRAP_ADDRESS on the ISIM server)

         System.setProperty("java.security.auth.login.config", "file:C:/ISIM6_Client/etc/jaas_login_was.conf");
    System.setProperty("com.ibm.CORBA.ConfigURL", "file: C:/ISIM6_Client/etc/sas.client.props");
    System.setProperty("com.ibm.SSL.ConfigURL", "file: C:/ISIM6_Client/etc/ssl.client.props");
    System.setProperty("com.ibm.CORBA.securityServerHost", hostname);
    System.setProperty("com.ibm.CORBA.securityServerPort", port);
    
    String appServerURL = "corbaloc:iiop:" + hostname + ":" + port;
    String platformContextFactory = "com.ibm.itim.apps.impl.websphere.WebSpherePlatformContextFactory";
    String itimRealm = "itimCustomRealm";
    String ejbUser = "isimsystem@" + itimRealm;        // Really important to include the @realm part
    String ejbPwd = "&lt;EJBPASSWORD&gt;";  (Check enRole.properties)

 
         Hashtable env = new java.util.Hashtable();
    env.put(InitialPlatformContext.CONTEXT_FACTORY, platformContextFactory);
    env.put(PlatformContext.PLATFORM_URL, appServerURL);
    env.put(PlatformContext.PLATFORM_PRINCIPAL, ejbUser);
    env.put(PlatformContext.PLATFORM_CREDENTIALS, ejbPwd);
    env.put(PlatformContext.PLATFORM_REALM, itimRealm);

         PlatformContext platform = null;
    
    try
    {
        platform = new InitialPlatformContext(env);
        System.out.println("Successfully got platform context");

     }
    catch(RemoteException e)
    {
        System.out.println("Error Class: " + e.getClass());
        System.out.println("Error Message: " + e.getMessage());
        System.out.println("Error LocalizedMessage: " + e.getLocalizedMessage());

     } catch (ApplicationException ex) {
            Logger.getLogger(IsimApiClient.class.getName()).log(Level.SEVERE, null, ex);
        }
        return platform;
    }

     private Subject getSubject() throws LoginException {

             String itimPswd = "&lt;ITIM MANAGER PASSWORD&gt;";
            String itimRealm = "itimCustomRealm";
            String itimUser = "itim manager";

             // Create the JAAS CallbackHandler
            CallbackHandler handler = null;
            if (itimRealm == null) {
                    System.out.println("getSubject: Realm is NULL");
                    handler = new WSCallbackHandlerImpl(itimUser, itimPswd);
            } else {
                    System.out.println("getSubject: Building callback handler: ["+itimUser+"]/["+itimRealm+"]/["+itimPswd+"]");
                    handler = new WSCallbackHandlerImpl(itimUser, itimRealm, itimPswd);
            }

             LoginContext loginContext = new LoginContext("WSLogin", handler);

             try
            {
                    System.out.println("Attempting login");
                    loginContext.login();
                    System.out.println("Logged in");
                    System.out.println("Obtained loginContext, subject is: " + loginContext.getSubject().toString());
            }
            catch(Exception e)
            {
                    System.out.println("Error Class: " + e.getClass());
                    System.out.println("Error Message: " + e.getMessage());
                    System.out.println("Error LocalizedMessage: " + e.getLocalizedMessage());

             }
            return loginContext.getSubject();
    }

 }

 

 

Errors

Some of the errors and solutions I have experienced along the way with this one;

Server ports – BOOTSTRAP_ADDRESS

If you don’t have the right port for your websphere server, then you can get the following error. In this case I have two clusters – one has the application server with a bootstrap port on 2811, the other on 2810 – my error below is when I mixed the two up (doh!).

SEVERE: null com.ibm.itim.apps.ApplicationException: 
Context: myserverCell01/clusters/ITIM_Messaging_Cluster, 
name: enroleejb.ManagedObjectImplHome: First component in name enroleejb.ManagedObjectImplHome not found. 
at com.ibm.itim.apps.identity.PersonMO.getData(PersonMO.java:149)

This error is similar – completely the wrong port and there is no process listening on it.  As usual, search for BOOTSTRAP_ADDRESS and locate the port for your Application Server WAS instance;

getSubject: Error Class: class com.ibm.websphere.security.auth.WSLoginFailedException getSubject: 
Error Message: SECJ0395E: Could not locate the SecurityServer at host/port:{0} to validate the userid and password entered. 
You might need to specify valid securityServerHost/Port in ${WAS_INSTALL_ROOT}/profiles/profile_name/properties/sas.client.props file. 
getSubject: Error LocalizedMessage: SECJ0395E: Could not locate the SecurityServer at host/port:{0} to validate the userid and password entered. 
You might need to specify valid securityServerHost/Port in ${WAS_INSTALL_ROOT}/profiles/profile_name/properties/sas.client.props file. loginContext: null

Name resolution

I remember this problem from back in the day with very old CORBA servers. What I didn’t realise was that my VPN connection from home was giving me a different reverse resolution to that in the office. Oh well – out with the hosts file in the end, but as usual it took a while to realise what the problem was.

It is also strange because I was providing the FQDN of my server in “com.ibm.CORBA.securityServerHost” and “PlatformContext.PLATFORM_URL”, but for some reason the client code was changing this to the shortname (at least that’s what appeared in the logs below). The problem in this case was environmental and some of my own making probably. While we have an ISIM cluster and load balancer in front of that for https traffic, the load balancer doesn’t handle the API port (BOOTSTRAP_PORT in WAS). So that meant I was doing this setup by going straight to a single AppServer. For some reason my VPN from home would resolve the AppServer hostname to IP correctly, but then when it attempted a reverse lookup for the IP address it got the load balancer FQDN back – no chance that would work! Two simple answers; 1) local hosts file to correct the name resolution while developing and 2) add the BOOTSTRAP_PORT to the loadbalancer configuration and use that fqdn instead. Either way the message is that you have to have your forward and reverse name resolution in place such that it resolves both ways to the same host/ip

My error;

getSubject: Error Class: class com.ibm.websphere.security.auth.WSLoginFailedException
getSubject: Error Message: SECJ0395E: Could not locate the SecurityServer at host/port:{0} to validate the userid and password entered. You might need to specify valid securityServerHost/Port in ${WAS_INSTALL_ROOT}/profiles/profile_name/properties/sas.client.props file.
getSubject: Error LocalizedMessage: SECJ0395E: Could not locate the SecurityServer at host/port:{0} to validate the userid and password entered. You might need to specify valid securityServerHost/Port in ${WAS_INSTALL_ROOT}/profiles/profile_name/properties/sas.client.props file.
loginContext: null

 

If you read this article from IBM, at the bottom it describes how to configure tracing for the corba client & doing this generated a log which turns the above ISIM message into something useful & that’s what showed me my UnknownHostException!;

11:08:57.801 com.ibm.CORBA.transport.TransportBase getHostIPAddress:134 P=735951:O=0:CT ORBRas[default]  java.net.UnknownHostException: MYHOSTNAME: MYHOSTNAME
    at java.net.InetAddress.getAllByName0(InetAddress.java:1384)
    at java.net.InetAddress.getAllByName(InetAddress.java:1293)
    at java.net.InetAddress.getAllByName(InetAddress.java:1218)
    at java.net.InetAddress.getByName(InetAddress.java:1168)
    at com.ibm.CORBA.transport.TransportBase.getHostIPAddress(TransportBase.java:132)

 

More errors

To be honest I had many, but didn’t capture them all. I’ve got more work to do on this client code so will update this post with what will be some more inevitable problems. That said, the configuration in this post should help you avoid most problems.

ITIM Custom Authentication

ITIM allows you to provide your own custom authentication module which can be very useful to meet corporate authentication standards – such as authenticating with an authoritative LDAP, database or perhaps & more likely, AD.

ITIM ships with an example for a custom AD authentication provider in ${ITIM_HOME}/extensions/examples/authentication/adauthentication which is a really good place to start.

This shipped example covers enough about how to create the module so there is no need to repeat that here, but there are a couple of things that are worth pointing out to make things go a little better.

First of all, make sure your authentication factory matches the interface definition exactly;

public class ADProviderFactory implements
 AuthenticationProviderFactory {

 public ADProviderFactory() { }

 public AuthenticationProvider getProvider(Hashtable env)
 throws ConfigurationException {
 return (AuthenticationProvider)new ADAuthenticationProvider(env);
 }
}

I’ve added in the class cast to AuthenticationProvider from the example that’s shipped to ensure there is no chance that the caller thinks that we’re not meeting the interface – although it certainly does work without it.

Personally I found the example a useful base for the ADAuthenticationProvider and ADAuthenticationHelper classes, but feel free to add your own customisation in there as needed.  In particular in a large enterprise there can be issues for *nix based ITIM servers using the example’s way of determining the AD domain controller to connect to.   Windows based ITIM servers won’t have this problem (I’ll try to make that into another post).

In the design of your AuthenticationProvider class I’d suggest considering some of the following;

  • The AuthenticationProvider has to end up returning a SystemUser.  It should really check that the ITIM user that’s returned is active.  It wouldn’t be great to allow active AD users to login to their inactive ITIM admin accounts…..gulp!
  • There is no need to be exclusive about the authentication provider.  You can try AD, then maybe go off and try a database, and perhaps if none of that works trying the plain old ITIM login itself.  Also, consider adding properties to the enRoleADAuthentication.properties file to control which providers are active.
  • If you don’t mind a slight performance loss, move the loadProperties() call in the example into the authenticate method.  That way all your properties are dynamic and you don’t have to restart ITIM to pick up flag changes.
  • Be sure to add sufficient logging so you can see your module functioning.  Don’t over do it because although ITIM admins logging in won’t cause problems, if you have a lot of JNDI or web services happening then you may make your logs too noisy.  Also make sure you don’t do the usual security give-away’s by telling an attacker that an account name is wrong, or just the password was wrong – authentication failures should just state that the authentication failed – nothing more.
  • Don’t expose unnecessary details about your user repositories in the logs either.  Generic names should suffice so you can monitor progress.

 

When it comes to deployment there are a couple of points to add in;

  • First of all, put your new module in the classes directory of each of your WebSphere application servers.  So that’s /opt/IBM/WebSphere/AppServer/profiles/AppSrv01/classes/ for a default install.  This is actually covered in an ISIM interim fix bit of doco update, but I’ve repeated it because it’s a pain to dig out the IV zip file just for that line.
  • Now, if you’re using a cluster you will also need to copy the jar into /opt/IBM/WebSphere/AppServer/profiles/DMgr/classes/.  If you don’t do this then you may see “class not found” errors for your AuthenticationProvider class when you’re in the WAS console.  Actually – I found these only when I was trying to change the ISIMSecurityDomain properties.
  • Finally, you need to add your new jar to the ITIM_LIB shared library.  Now, this isn’t that obvious at first, but it would appear that there are multiple classloaders working in ISIM so what I found is that login/authentication works fine, web services work fine, but JNDI calls from TDI don’t.  The solution is to add the class to ITIM_LIB.  The error I had was;
  • com.myorg.adauthentication.ADProviderFactory incompatible with com.ibm.itim.authentication.AuthenticationProviderFactory ( class java.lang.ClassCastException)

Now that’s a little unpleasant, and it directs you back to your AuthenticationProviderFactory.  This problem is actually a classloader problem.  Because the classloader that is the login page loads the class and then the classloader that is the JNDI event handler also loads the class, they consider the classes to be different and then throw this exception.  So, by putting the jar into the ITIM_LIB shared library, you’re allowing the parent classloader to load the class – then when the child classloaders load it they can resolve the class to the common type that the parent loaded and this exception is avoided.  Ok – that was the longest explanation for putting something into ITIM_LIB ever…..

 

Now – with that in hand, restart the application servers (and the DMgr) if you’re in a cluster) and you should be ok.  I would  expect that you could use a symlink to the jar to avoid having multiple copies of the jar (not tested so don’t shoot me).

That’s it – you should now have your custom authentication provider working and authenticating your various clients – user’s logging in, web services, JNDI, etc…

 

 

 

 

JNDI Feed quick reference

A quick list of the main points to remember when using the ibm.JNDI connector to ISIM;

General

Provider URL: http://yourhost:9080/enrole/dsml2_event_handler

JNDI Driver: com.ibm.dsml2.jndi.DSML2InitialContextFactory

Create an IDI feed service in ISIM, switch on “use workflow” to make sure workflows are run(!), set the naming context to be “dc=<yourNamingContext>” – this is used by the TDI AL’s

Include only the objectclass that maps to the Entity you want to create.  If you pass multiple then ISIM will choose one of them & generally it won’t be the one you want(!).

Ensure the ITIM service “user id” and password match those provided in the “Connection” tab in TDI.

 Add

In the output map provide $dn which should be in the form “uid=<uid for identity>,dc=serrviceNamingContext”

Map whatever attributes you want, or (and provided you validate the attribute list first!), map all attributes so that the AL isn’t affected by schema changes.

 Modify

Set a link criteria to be;

ret.filter="(uid=" + work.getString("uid") + ")";

Set the search base to be the naming context of the IDI service in ITIM i.e. dc=<yourNamingContext>).

In the output map, set $dn “Add” to true and “Mod” to false.  Also set objectclass the same way.

You have to provide attributes in the attribute map for modify.  You can provide the whole schema if required (drag & drop) – ITIM will only update attributes that aren’t null.  Again, this is useful to protect against schema changes breaking the JNDI feed.

Delete

Set a link criteria to be;

ret.filter="(uid=" + work.getString("uid") + ")";

Set the search base to be the naming context of the IDI service in ITIM i.e. dc=<yourNamingContext>).

The Input map can be blank – it’s a delete after all.

Ok – that was the brutally cut down version, but it should be enough….

 

Problems and solutions

“com.ibm.dsml2.jndi.DSML2NamingException: Server returned HTTP response code: 401 “ means that the username/password used to access the ITIM service is incorrect. HTTP code 401 is “Unauthorized” after all.

Under the “Advanced” part of the connector, set the following;

  • Authentication Method: Simple
  • Login username: <Match the User ID field in the service definition form in ITIM>
  • Login password: <Match the Password field in the service definition form in ITIM>

“The following exception occured while validating field: _batchRequestTypeChoiceList of class: com.ibm.dsml2.parser.BatchRequest'”

  •  Again, this can be an authentication issue.  Check the username / password on the JNDI connector.
  • It can also be caused by the “Link criteria” being specified incorrectly.  The link criteria should be specified as a valid LDAP filter, including brackets.  A simple mistake would be to specify a link criteria of “uid=myUserName”, this should be “(uid=myUserName)” (without quotes of course.

TDI 6.1.1 – java.io.File delete method

The java.io.File class is a really useful class for those times when you need a little more in terms of file manipulation than the ‘system’ object UserFunctions provide.

However, using this in TDI will throw up a little gotcha that can occur when using the delete method.

For example;

var dirObj = new java.io.File("c:\\temp\\mydir");
dirObj.delete();

Will generate the following error;

com.ibm.jscript.parser.ParseException: Encountered "delete" at line..

The answer is to de-reference the method;

var dirObj = new java.io.File("c:\\temp\\mydir");
dirObj["delete"]();

 

Stop DB2 on Linux

Just a few useful commands to shutdown DB2 on Linux ready to apply fixpacks.  This relates to a shutdown of the default instance db2inst1, but can be applied to any running instance.

First, shutdown the instance;

su - db2inst1
. /home/db2inst1/sqllib/db2profile
db2 force applications all
db2 terminate
db2stop

Next, shutdown the DB2 administration server;

su - dasusr1
. /home/dasusr1/das/dasprofile
db2admin stop

Finally shutdown the fault management daemon;

db2fmcu -d
db2fm -d

IBM TDI & JSON

Although TDI 7.1 FP5 ships with a JSON parser, it would appear that the parser doesn’t actually work (even at FP6).  The example AL that is shipped doesn’t work either – they both generate the same error – it looks like the JSON parser doesn’t quite register properly within the AL;

15:30:32,468 ERROR - [WriteJSON] CTGDIS810E handleException - cannot handle exception , initialize 
java.lang.Exception: CTGDIS159E This Connector has no configured Parser. 
        at com.ibm.di.connector.
Connector.initParser(Connector.java:567) 
        at com.ibm.di.connector.FileConnector.openWriteFile(FileConnector.java:343)

This may be resolved in 7.1.1, but in this environment that version isn’t available.

Of course there is a workaround in the form of JSON Simple.  Drop the JSON Simple json-simple-1.1.1.jar into the “<IDI install dir>\jars” folder & restart TDI to make it available.

JSON Encoding

This is a simple approach which encodes an object as a JSON string.  The encoding will accept quite complex structures too.  I have it quite happily accepting a java.util.LinkedList where each element is itself a HashMap.  The only trick is that the JS object need to have been initialised properly;

// Init the object
var theObject = org.json.simple.JSONValue.parse("{}");

// Populate the object
theObject.name = "Mr Object";
theObject.tel = "999 ObjectHelp";

// Get the JSON string
var jsonString = org.json.simple.JSONValue.toJSONString(theObject);

JSON decoding

To decode JSON – again use a FileSystem connector & the following code;

var jsonString = work.getString("jsonString");
if ((jsonString != "") && (jsonString != null)) {
    var jsonWrapper = org.json.simple.JSONValue.parse(jsonString);
    g_QueryResult = jsonWrapper[0];
}

Generating JSON obviously has many uses, but in this case I was generating data to be consumed by a couple of jquery plugins – DataTables and jqplot.  More on how to generate the correct structure for those plugins later.