In order to call CICS from Java, we've traditionally used RDz to generate a J2C bean via an Ant script. But I don't have RDz anymore (it doesn't support Groovy and always running older versions of Eclipse), so my only alternative was to use a JCL to generate a bean via IBM's JZOS library.
I'm still not a big fan, it takes time to run the JCL, download the generated file from the mainframe and tweak the object names, packages, etc.. What if I could just declare my copylib structure in my Java code and call CICS? To that end, I've been working on a parser which converts a byte stream to/from a map.
Leaving the CICS call of it out for the moment, here's an idea of want the read code would look like.
def slurper = new CopybookSlurper("""\ 01 TESTING-COPYBOOK. 03 STATES OCCURS 10 TIMES . 05 STATE PIC XX. """) def data = slurper.getReader('MIOHINWINVCAKYFLNCSC'.getBytes()) data.STATES.each { print it.STATE //will print each two byte state
} assert 'IN' == data.STATES[2].STATE //zero-based indexed of the STATES list
And here's the write code
def slurper = new CopybookSlurper("""\ 01 TOP-LEVEL. 03 STATES OCCURS 2 TIMES. 05 STATE-NUM PIC X(3). 05 STATE-NAME PIC X(10). 05 NUMERIC-STATE PIC 9(2). """) def writer = slurper.getWriter(new byte[slurper.length]) writer.with { STATES[0].with { STATE_NUM = '1' STATE_NAME = 'MICHIGAN' NUMERIC_STATE = 2 } STATES[1].with { STATE_NUM = '2' STATE_NAME = 'OHIO' NUMERIC_STATE = 3 } } assert new String(writer.getBytes(), 'IBM-37') ==
'1 MICHIGAN 022 OHIO 03'
The slurper name and map structure is inspired by Groovy's XmlSlurper and JsonSlurper. My parser implementation is written in groovy to take advantage of the dynamic nature of the maps and closures. I used the JZOS library under the covers to handle parsing the individual data elements so it can handle binary fields (COMP-3 stuff). Unfortunately JZOS didn't provide any simple hooks for getting a data type based on a PIC definition, so I had to rewrite that logic. I know my parser isn't as robust as the IBM version, it doesn't handle some of the weird datatypes for instance, but it's good enough for the normal copybooks that we'd be using from the web.
I also wanted to make my map lazy so that the bytes are only parsed if requested, after all sometimes COBOL copybooks have a ton of data in them, and I only want a small piece. So rather than parsing the field right away, the parser creates a map of closures with instructions on how to parse the data. Likewise, the writer stores a closure for each variable with instructions on how to write data to a shared byte array. The getReader method is shown below. There's a lot of complexity involved in occurrences and groups that you can't see here, but I just wanted to show the mapping of the variable names to closures.
public Map<String, ?> getReader(byte [] bytes) { Map readers = [:] fieldProcessors.each { DataVariable variable -> if (variable.read) { readers[variable.name] = { variable.read(bytes) } } } return new LazyProperties(readers, bytes) }
The LazyProperties class extends HashMap and invokes the closures from the map when referenced, then caches the results for the next call. The getWriter method works in a similar fashion with closures, though it doesn't cache results because any call to the write methods should write data to the byte stream.
That's what I have working so far, the next step is to look into the javax.resource.cci.Record and javax.resource.cci.Streamable interfaces and perhaps Spring's org.springframework.jca.cci.core.support.CciDaoSupport to call down to CICS.