Welcome to XMLSERVICE’s documentation!

XMLSERVICE Introduction

Goto Main Page

XMLSERVICE Introduction

XMLSERVICE is Open Source RPG, created for web programming simplicity and deployment flexibility, any language, any transport, avoiding complexities dealing with big package ‘web services’. Internally, XMLSERVICE is designed Plain Old XML (POX), which enables simple REST XML protocol, avoiding complexity of SOAP/WSDL based Web Services. XMLSERVICE is 100% RPG, never requires Java, PHP, or any other front end language to serve your IBM i RPG programs to the web. In fact, XMLSERVICE is so simple, that you can almost run your entire IBM i machine using only simple HTML/XML (demonstrated later section).

XMLSERVICE is a single library of Open Source RPG code that enables XML scripting calls of System i resources. XMLSERVICE RPG server library does not require other language products to run on your IBM i, however language teams often provide a client toolkit to greatly simplify XML calls to XMLSERVICE.

XMLSERVICE, as name implies, enables XML services on your IBM i machine. XMLSERVICE library is simply a collection of Open Source RPG modules that essentially allow your to access anything on your IBM i machine, assuming proper profile authority. Simply stated, XMLSERVICE accepts XML document containing actions/parameters (<pgm>,<cmd>,<sh>,<sql>,etc.), performs requested operations on IBM i, then sends an XML document of results back to the client. This simple concept has become a powerful tool for scripting language clients on IBM i including Zend Server PHP Toolkit and PowerRuby Toolkit. However, PHP and Ruby are not unique to XMLSERVICE, XML is a universal protocol support by nearly every language, therefore nearly any language can use XMLSERVICE, and most any transport between client/server.

Installation

Build requirements

Building requires Python 3 and GNU make. These can be installed with yum: yum install python3 make-gnu

You will also need the ILE RPG compiler installed (5770-WDS option 31) along with the following PTFs:

7.3: SI62605
7.2: SI62604
7.1: SI62580

Building

PATH=/QOpenSys/pkgs/bin:$PATH

git clone https://github.com/IBM/xmlservice.git

cd xmlservice

python3 ./configure

make

Customizing the Build

You can customize the build by passing options to configure:

--library: set the build library
--debug: set the debug level (DBGVIEW CL parameter)

REST interface

Optional: Set up your Apache to enable XMLSERVICE REST using RPG program XMLCGI.PGM included in XMLSERVICE installation, then restart your Apache instance.

/www/apachedft/conf/httpd.conf:
ScriptAlias /cgi-bin/ /QSYS.LIB/XMLSERVICE.LIB/
<Directory /QSYS.LIB/XMLSERVICE.LIB/>
  AllowOverride None
  order allow,deny
  allow from all
  SetHandler cgi-script
  Options +ExecCGI
</Directory>

stop/start Apache instance:
endTCPSVR SERVER(*HTTP) HTTPSVR(APACHEDFT)
STRTCPSVR SERVER(*HTTP) HTTPSVR(APACHEDFT)

Operation and transports

XMLSERVICE XML documents can be transported between IBM i and clients over any connection, any language.

XMLSERVICE APIs (included)

XMLSERVICE library includes language transports for popular REST and DB2 connections, which fulfills needs for most internet services applications.

  • XMLSERVICE/XMLCGI.PGM– RPG CGI HTTP/REST method GET or POST (traditional web service interface)

  • XMLSERVICE/XMLSTOREDP.SRVPGM – RPG DB2 stored procedure (IBM i’s premier DB2 for i)

    • DB2 drivers local/remote with stored procedure IN/OUT capabilities (traditional DB2 interface)
    iPLUG4K  (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CHAR(4064),  OUT XMLOUT CHAR(4064))
    iPLUG32K (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(32000), OUT XMLOUT CLOB(32000))
    iPLUG65K (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(65K),   OUT XMLOUT CLOB(65K))
    iPLUG512K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(512K),  OUT XMLOUT CLOB(512K))
    iPLUG1M  (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(1M),    OUT XMLOUT CLOB(1M))
    iPLUG5M  (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(5M),    OUT XMLOUT CLOB(5M))
    iPLUG10M (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(10M),   OUT XMLOUT CLOB(10M))
    iPLUG15M (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(15M),   OUT XMLOUT CLOB(15M))
    
    • DB2 drivers local/remote without stored procedure IN/OUT capabilities (loop fetch required)
    iPLUGR4K  (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CHAR(4064))
    iPLUGR32K (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(32000))
    iPLUGR65K (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(65K))
    iPLUGR512K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(512K))
    iPLUGR1M  (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(1M))
    iPLUGR5M  (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(5M))
    iPLUGR10M (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(10M))
    iPLUGR15M (IN IPC CHAR(1024), IN CTL CHAR(1024), IN XMLIN CLOB(15M))
    
  • XMLSERVICE/XMLSTOREDP.SRVPGM – optional custom transport (programmers only)

    • if included XMLSERVICE transports do not fill your need, please feel free to create your own (sockets, data queues, ftp, etc.). Multiple entry APIs exist in XMLSERVICE that you may find useful:
    xmlstoredp.srvpgm - *SRVPGM interface for calls
    
      Native stored procedure call target (iPLUG4K - iPLUG15M):
        D iPLUG4K         PR             1N   extproc(*CL:'iPLUG4K')
        D pIPC                        1024A
        D pCtl                        1024A
        D pXmlIn                          *
        D pXmlOut                         *
    
      RPG call target:
        D runClient       PR             1N
        D   pIPCSP                    1024A
        D   pCtl                      1024A
        D   pIClob                        *
        D   szIClob                     10i 0
        D   pOClob                        *
        D   szOClob                     10i 0
    
      PASE call target (also use RPG when CCSID issues):
        D runASCII        PR             1N
        D   pIPCSP2                       *
        D   szIPCSP2                    10i 0
        D   pCtlSP2                       *
        D   szCtlSP2                    10i 0
        D   pIClob2                       *
        D   szIClob2                    10i 0
        D   pOClob2                       *
        D   szOClob2                    10i 0
        D   ccsidPASE2                  10i 0
        D   ccsidILE2                   10i 0
    

IBM i CCSID 65535

XMLSERVICE REST and DB2 connections have implicit CCSID conversion between client and server, therefore your XMLIN/XMLOUT XML document will be implicitly CCSID converted by the transport layer, to wit, XMLSERVICE should just work. However, IBM i CCSID 65535 (hex), will destroy the entire XMLSERVICE scheme, and you will witness horrible hangs, junk data, dead processes, etc., so please take action on your IBM i to live with the modern ASCII world (all clients, all new scripting languages, remote/local including PASE).

Check your IBM i for CCSID convert safety.

If you see DSPSYSVAL 65535, you have a big problem with the ASCII world, but you can take corrective IBM i action.

DSPSYSVAL SYSVAL(QCCSID)
Coded character set
  identifier . . . . . :   65535      1-65535

Here are some corrective IBM i suggestions (CCSID 37 an example), but remember you have to end current running jobs and restart for the CCSID changes to enable (including XTOOLKIT jobs):

  • change system ccsid
CHGSYSVAL SYSVAL(QCCSID) VALUE(37)
  • change Apache instances (/www/instance/httpd.conf)
# protect FastCGI against bad CCSID (dspsysval qccsid 65535)
DefaultFsCCSID 37
CGIJobCCSID 37
  • change user profile(s)
CHGUSRPRF USRPRF(FRED) CCSID(37)

Connection public or private

XMLSERVICE parameters CTL and IPC enable two types of connections.

  • public connection – XMLSERVICE stateless, many jobs, every use a fresh start
    • CTL='*here' , IPC="*NA"
      • “public” because any IBM i profile use is a fresh start
      • life scope – life of a single XML IN/OUT request
  • private connection – XMLSERVICE state full, single job, where a given profile is routed to XTOOLKIT job(s)
    • CTL='*sbmjob' , IPC="/tmp/myjob1"
      • CTL='*sbmjob' – if not running, submit a new XTOOLKIT job
      • IPC="/tmp/myjob1" – all requests of IPC route to XTOOLKIT job (*sbmjob)
      • “private” because ONE IBM i profile owns XTOOLKIT job, any other IBM i profile will fail to attach
      • life scope – forever, until ended by operator or user program ends XTOOLKIT job (like a 5250)

When to use XMLSERVICE public or private?

Of course any discussion that presumes to predict usage is likely to have legitimate user exception, but a guideline may be appropriate for use of XMLSERVICE public vs. private connection.

Web programming style (public, stateless)

XMLSERVICE public connections are generally used when IBM i resource can be called once by many different user profiles, current data returned, no lasting persistent data needed by the IBM i resource. The programmer meta description of transient resource services is “stateless programming”.

XMLSERVICE public “stateless” (CTL=’*here’, IPC=’*NA’)

  • profile FRED (any public QSQ)
  • profile SALLY (any public QSQ)
  • profile RITA (any public QSQ)
  • profile XAVIER (any public QSQ)

XMLSTOREDP->XMLSERVICE (QSQ)

  • QSQ temporary profile use (stateless)
  • QSQ return to pool on script end
  • XMLSERVICE restart every request (web style)

Although very handy, clean wrkactjob, no hanging locks, etc., public connection is not a high performance use of XMLSERVICE.

  • public connection – XMLSERVICE stateless, many jobs, every use a fresh start
    • CTL='*here' , IPC="*NA" – profile FRED (RPG company stock service)
    • CTL='*here' , IPC="*NA" – profile SALLY (RPG IRS flat tax rate service)
    • CTL='*here' , IPC="*NA" – profile RITA (RPG calculate pound to kilo)
    • CTL='*here' , IPC="*NA" – profile XAVIER (RPG company stock service)

Traditional programming style (private, state full)

XMLSERVICE private connections are generally used when IBM i resource will be called many times by the same user profile, lasting persistent data needed by the IBM i resource (RPG variables, open files, etc.). The programmer meta description of required data services is “state full programming”.

XMLSERVICE private “state full” (CTL=’*sbmjob’, IPC=’/tmp/xxxx’)

  • profile FRED XTOOLKIT myjob1,myjob2 (private)
  • profile SALLY XTOOLKIT sallyjob1 (private)
  • profile RITA XTOOLKIT nursejob (private)
  • profile XAVIER XTOOLKIT xjob1 - xjob5 (private)

XMLSTOREDP (QSQ)

  • QSQ temporary profile use (stateless)
  • QSQ return to pool on script end

XMLSERVICE (XTOOLKIT)

  • XTOOLKIT owned by profile (private)
  • XTOOLKIT job never ends (until killed)
  • XTOOLKIT full state programming (5250 style)

Traditional RPG programs usually need to track local variables, open files, etc., between requests, both for correct functionality and performance. The XTOOLKIT jobs that result from CTL='*sbmjob' , IPC="/tmp/xxxx" are similar to 5250 jobs, where a user profile signs on, then uses 5250 job to run other programs (aka XMLSERVICE design), also, like multiple 5250 sessions (PC emulators), many different XTOOLKIT jobs can be used by the same user profile.

  • private connection – XMLSERVICE state full, single job, where a given profile is routed to same XTOOLKIT job

    • CTL='*sbmjob' , IPC="/tmp/myjob1" – profile FRED XTOOLKIT myjob1 (Fred’s use only RPG payroll application)
    • CTL='*sbmjob' , IPC="/tmp/myjob2" – profile FRED XTOOLKIT myjob2 (Fred’s use only RPG inventory application)
    • CTL='*sbmjob' , IPC="/tmp/sallyjob1" – profile SALLY XTOOLKIT sallyjob1 (Sally’s use only RPG admissions application)
    • CTL='*sbmjob' , IPC="/tmp/nursejob" – profile RITA XTOOLKIT nursejob (Rita’s use only RPG nurse scheduling application)
    • CTL='*sbmjob' , IPC="/tmp/xjob1" – profile XAVIER XTOOLKIT xjob1 (Xavier’s use only RPG payroll application)
    • CTL='*sbmjob' , IPC="/tmp/xjob2" – profile XAVIER XTOOLKIT xjob2 (Xavier’s use only RPG inventory application)
    • CTL='*sbmjob' , IPC="/tmp/xjob3" – profile XAVIER XTOOLKIT xjob3 (Xavier’s use only RPG admissions application)
    • CTL='*sbmjob' , IPC="/tmp/xjob4" – profile XAVIER XTOOLKIT xjob4 (Xavier’s use only RPG nurse scheduling application)
    • CTL='*sbmjob' , IPC="/tmp/xjob5" – profile XAVIER XTOOLKIT xjob5 (Xavier’s use only RPG super hero application)
    • IBM i programmer: Profile Xavier is using many XTOOLKIT jobs (xjob1 - xjob5), many different applications, but Xavier cannot use Fred’s, Sally’s or Rita’s XTOOLKIT jobs (myjob1,myjob2,sallyjob1,nursejob), because Xavier does not own other profile XTOOLKIT jobs. XTOOLKIT jobs should be an easy pattern for RPG programmers familiar with single session 5250 job(s), owned by a profile, one thing at a time (not threaded), long running RPG programs, many IBM i files open, etc.
      • However, XTOOLKIT jobs have an interesting characteristic that 5250 emulator jobs cannot match, profile owned XTOOLKIT jobs can be accessed by many different client devices all at the same time, to wit, Xavier can use a laptop and a smart phone to all jobs at the same time (xjob1 - xjob5), or Xavier can leave his work laptop running (connected), go home, have dinner, and continue working all XTOOLKIT jobs from the family iPAD.
    • IBM i operator: You may wrkactjob and kill *immed XTOOLKIT jobs (same as 5250).
      • However, an ipcs administrative cleaner solution may suggest you write a custom XMLSERVICE program CTL='*immed' , IPC="/tmp/myjob1", to remove “in the way” XTOOLKIT jobs (suggestion only).

Open Source goal

XMLSERVICE is constantly growing Open Source, so new functions are added over time. XMLSERVICE goal is never impact existing applications with new features, to date, XML’s natural ability to add keywords and attributes has been very successful in keeping this goal.

XMLSERVICE CMDs

<?xml version='1.0'?>
<script>
<cmd exec='rexx'>RTVJOBA USRLIBL(?) SYSLIBL(?)</cmd>
<cmd>DLTDTAQ DTAQ(MYLIB/MYDATAQ)</cmd>
<cmd>CRTDTAQ DTAQ(MYLIB/MYDATAQ) MAXLEN(100) AUT(*EXCLUDE)</cmd>
</script>

XMLSERVICE PASE

<?xml version='1.0'?>
<script>
<sh rows='on'>/QOpenSys/usr/bin/ls -l /tmp</sh>
<sh rows='on'>/QOpenSys/usr/bin/system -i 'wrkactjob'</sh>
</script>

XMLSERVICE DB2

<?xml version='1.0'?>
<script>
<sql>
<options options='noauto' autocommit='off'/>
<connect conn='myconn' options='noauto'/>
<query conn='myconn' stmt='myupdate'>UPDATE animal SET id = 9 where ID = 3</query>
<query conn='myconn' stmt='myselect'>select count(*) from animal where ID = 9</query>
<fetch stmt='myselect' block='all' desc='off'/>
<free stmt='myselect'/>
<commit conn='myconn' action='rollback'/>
<query conn='myconn' stmt='myselect'>select count(*) from animal where ID = 9</query>
<fetch stmt='myselect' block='all' desc='off'/>
<free/>
</sql>
</script>

XMLSERVICE PGMs, SRVPGMs, APIs

PGM:
<?xml version='1.0'?>
<script>
<pgm name='ZZCALL' lib='XMLSERVICE'>
<parm  io='both'>
  <data type='1A' var='INCHARA'>a</data>
</parm>
<parm  io='both'>
  <data type='1A' var='INCHARB'>b</data>
</parm>
<parm  io='both'>
  <data type='7p4' var='INDEC1'>11.1111</data>
</parm>
<parm  io='both'>
  <data type='12p2' var='INDEC2'>222.22</data>
</parm>
<parm  io='both'>
  <ds>
  <data type='1A' var='INDS1.DSCHARA'>x</data>
  <data type='1A' var='INDS1.DSCHARB'>y</data>
  <data type='7p4' var='INDS1.DSDEC1'>66.6666</data>
  <data type='12p2' var='INDS1.DSDEC2'>77777.77</data>
  </ds>
</parm>
<return>
  <data type='10i0'>0</data>
</return>
</pgm>
</script>

SRVPGM:
<?xml version='1.0'?>
<script>
<pgm name='ZZSRV' lib='XMLSERVICE' func='ZZVARY'>
<parm comment='search this name' io='in'>
  <data var='myName' type='10A' varying='on'><![CDATA[<Ranger>]]></data>
</parm>
<return>
  <data var='myNameis' type='20A' varying='on'><![CDATA[<Mud>]]></data>
</return>
</pgm>
</script>

System APIs:
<?xml version='1.0'?>
<script>
<pgm name='QSNDDTAQ'>
<parm io='in'>
  <data type='10A'>MYDATAQ</data>
</parm>
<parm  io='in'>
  <data type='10A'>XMLSERVICE</data>
</parm>
<parm  io='in'>
  <data type='5p0'>50</data>
</parm>
<parm  io='in'>
  <data type='100A'>System i data queues forever</data>
</parm>
</pgm>
</script>

XMLSERVICE HTML/XML interface

Using your IBM i and XMSERVICE download (see installation), without writing one line of code in any language, we can already check out XMLSERVICE functions using HTML/XML forms. We should note, that by any standard the following trivial XMLSERVICE example is clearly REST web services, but no SOAP, no WSDL, no Java, no PHP, no Ruby, nothing but 2 cents worth of HTML/XML.

XMLSERVICE input - Plain Old XML input to XMLSERVICE for request select * from db2/animals.

<myscript>
<sql>
<query>select * from db2/animals</query>
<fetch block='all' desc='on'></fetch>
</sql>
</myscript>

XMLSERVICE output - Plain Old XML output from XMLSERVICE with records returned from db2/animals.

<myscript>
<query conn='conn1' stmt='stmt1'>
<success><![CDATA[+++ success select * from db2/animals]]></success>
</query>
<fetch block='all' desc='on' stmt='stmt1'>
<row>
<data desc='ID'><![CDATA[0]]></data>
<data desc='BREED'><![CDATA[cat]]></data>
<data desc='NAME'><![CDATA[Pook]]></data>
<data desc='WEIGHT'><![CDATA[3.20]]></data>
</row>
</myscript>

Instructions for your IBM i machine

Step 1) - Add XMLSERVICE to any Apache instance (APACHEDFT)

Set up your Apache to enable XMLSERVICE REST using RPG program XMLCGI.PGM included in XMLSERVICE installation, then restart your Apache instance.

/www/apachedft/conf/httpd.conf:
ScriptAlias /cgi-bin/ /QSYS.LIB/XMLSERVICE.LIB/
<Directory /QSYS.LIB/XMLSERVICE.LIB/>
  AllowOverride None
  order allow,deny
  allow from all
  SetHandler cgi-script
  Options +ExecCGI
</Directory>

start Apache instance:
STRTCPSVR SERVER(*HTTP) HTTPSVR(APACHEDFT)

Step 2) - Ready to use XMLSERVICE for HTML/XML

Cut/Paste following HTML/XML form to your laptop Desktop/strsql.html:

  • change action target to your actual machine action="http://myibmi/cgi-bin/xmlcgi.pgm"
  • point your favorite browser at the HTML file file:///home/adc/Desktop/strsql.html
    • enter database (*LOCAL), user (your profile), password (your password)
    • enter a SQL query in HTML strsql command and press button STRSQL
desktop/strsql.html:
<html>
<head>
<script>
function getVal() {
xml = "<?xml version='1.0'?>";
xml += "<myscript>";
xml += "<sql>";
xml += "<query>";
xml += document.getElementById('strsql').value;
xml += "</query>";
xml += "<fetch block='all' desc='on'>";
xml += "</fetch>";
xml += "</sql>";
xml += "</myscript>";
document.getElementById('xmlin').value = xml;
}
</script>
</head>
<body>
<h3>STRSQL</h3>
<form onsubmit="getVal();" name="input" action="http://myibmi/cgi-bin/xmlcgi.pgm" method="post">
<br><input type="text" name="db2" value="*LOCAL" size="40" > database
<br><input type="text" name="uid" value="MYUID" size="40" > user
<br><input type="password" name="pwd" value="MYPWD" size="40" > password
<input type="hidden" name="ipc" value="*NA">
<input type="hidden" name="ctl" value="*here *cdata">
<input type="hidden" name="xmlin" id="xmlin" value="na">
<input type="hidden" name="xmlout" value="500000">
<br><input type="text" name="strsql" id="strsql" size="40" /> strsql command (select * from db2/animals)
</table>
<br><br><input type="submit" value="STRSQL" />
</form>
</body>
</html>

desktop/strsql.html example

As strsql.html name implies, this simple html enables STRSQL running from your laptop to your IBM i. Enter any SQL statement you wish in the html form and XMLSERVICE will run just like STRSQL green screen when you press the STRSQL button. XMLSERVICE output returned will be XML (of course), so if your browser has issues displaying XML, you may have to view page source.

HTML form strsql.html uses simple JavaScript function getVal() with document.getElementById('strsql').value, which reads HTML text input <input id='strsql' and adds user SQL request to XML document to HTML text input <input id="xmlin". All XMLSERVICE required REST tag elements can bee seen in the following HTML form http://myibmi/cgi-bin/xmlcgi.pgm?db2=*LOCAL@uid=MYUID@pwd=MYPWD@ipc=*NA@ctl="*here *cdata"@xmlin=(see JavaScript)@xmlout=500000, but we will cover this in later sections.

What is happening?

If we change method="post" to method="get", the fully encoded document will appear on the browser URL line. As you can see, when input arrives from our browser (or REST client), XMLSERVICE/XMLCGI.PGM has much HTTP decoding before actually parsing the XML document and servicing the request.

<form onsubmit="getVal();" name="input" action="http://myibmi/cgi-bin/xmlcgi.pgm" method="post">
-- change --
<form onsubmit="getVal();" name="input" action="http://myibmi/cgi-bin/xmlcgi.pgm" method="get">

The follow cut/paste is one continuous browser line (split for viewing):
http://myibmi/cgi-bin/xmlcgi.pgm?db2=*LOCAL
&uid=MYUID
&pwd=MYPWD
&ipc=*NA
&ctl=*here+*cdata
&xmlin=%3C%3Fxml+version%3D%271.0%27%3F%3E
      %3Cmyscript%3E
      %3Csql%3E
      %3Cquery%3Eselect+*+from+db2%2Fanimals
      %3C%2Fquery%3E
      %3Cfetch+block%3D%27all%27+desc%3D%27on%27%3E
      %3C%2Ffetch%3E
      %3C%2Fsql%3E
      %3C%2Fmyscript%3E
&xmlout=500000
&strsql=select+*+from+db2%2Fanimals

The flow:

  • We point our browser file:///home/adc/Desktop/strsql.html, enter SQL query and press STRSQL button
  • IBM i Apache XMLCGI.PGM receives our encoded HTML/XML form action="http://myibmi/cgi-bin/xmlcgi.pgm"
  • XMLCGI.PGM calls XMLSERVICE.PGM (using DB2 stored procedures iPLUGxxx, but you do not need to know this yet).
  • XMLSERVICE.PGM parses XML input and runs internal DB2 driver <sql><query>...</query></sql>.
  • XMLSERVICE.PGM parses result set from DB2 driver into output XML document <sql><fetch/></sql>
  • browser sees XML return of DB2 data

XMLSERVICE is Open Source, so you can examine internals of XMLCGI.PGM, for now , we simply need to understand XMLCGI.PGM decodes HTML/XML document, passes XML document request to XMLSERVICE.PGM (DB2 request example), and returns XML output to client (browser). If REST client is not a browser, but a scripting language like PHP or Ruby, exact same sequence occurs, except additionally most languages offer an XML parser to parse output XML into variables or structures (PHP Toolkit or Ruby Toolkit).

Quick test functions HTML/XML

XMLSERVICE HTML/XML technique can be used for nearly anything on IBM i machine CMD, PGM, SRVPGM, system APIs, PASE utilities, DB2, etc. Feel free to copy strsql.html form, modify, and try other XMLSERVICE functions <myscript>other functions</myscript>. HTML/XML technique is a very handy testing a potential XMLSERVICE program service without writing a line of code, and, clearly demonstrates elegant simplicity embodied by XMLSERVICE.

XMLSERVICE DB2 interface

DB2 connection is not a web service, but many languages support high speed DB2 local/remote requests, so XMLSERVICE included a stored procedures interface (iPLUG4K - iPLUG15M). The nature of DB2 stored procedures requires a size specified on in/out parameters, therefore XMLSERVICE library includes various iPLUGxx sizes to fit your XML document data needs (4K, 32K, 65K, 512K, 1M, 5M, 10M, up to 15M).

We should note, XMLSERVICE DB2 is much faster over REST interface, so many language toolkits offer DB2 connection as the premier service.

  • RPG DB2 (no toolkit)
myIPC = '/tmp/thebears1';
myCtl = '*sbmjob';
// call XMLSERVICE/ZZCALL(...) using XML only
myXmlIn =
  '<?xml version="1.0" encoding="ISO-8859-1"?>'         + x'0D'
+ '<script>'                                            + x'0D'
+ '<pgm name="ZZCALL" lib="XMLSERVICE">'                + x'0D'
+ '<parm><data type="1a" v="1">Y</data></parm>'         + x'0D'
+ '<parm><data type="1a" v="2">Z</data></parm>'         + x'0D'
+ '<parm><data type="7p4" v="3">001.0001</data></parm>' + x'0D'
+ '<parm><data type="12p2" v="4">0003.04</data></parm>' + x'0D'
+ '<parm>'                                              + x'0D'
+ ' <ds>'                                               + x'0D'
+ '  <data type="1a" v="5">A</data>'                    + x'0D'
+ '  <data type="1a" v="6">B</data>'                    + x'0D'
+ '  <data type="7p4" v="7">005.0007</data>'            + x'0D'
+ '  <data type="12p2" v="8">0000000006.08</data>'      + x'0D'
+ ' </ds>'                                              + x'0D'
+ '</parm>'                                             + x'0D'
+ '</pgm>'                                              + x'0D'
+ '</script>'                                           + x'00';
myXmlOut = *BLANKS;
// make call to XMLSERVICE provided stored procedure(s)
// sizes from iPLUG4k to iPLUG15M (see crtsql xmlservice package)
Exec Sql call XMLSERVICE/iPLUG4K(:myIPC,:myCtl,:myXmlIn,:myXmlOut);
  • PHP DB2 (toolkit)
require_once("ToolkitService.php");
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(
array('InternalKey'=>$ipc,         // route to same XMLSERVICE job /tmp/myjob1
'subsystem'=>"QGPL/QDFTJOBD",      // subsystem/jobd to start XMLSERVICE (if not running)
'plug'=>"32K"));                   // max size data i/o (iPLUG4K,32K,65K, 512K,1M,5M,10M,15M)
$param[] = $ToolkitServiceObj->AddParameterChar   ('both',  1,  'INCHARA', 'var1', 'Y');
$param[] = $ToolkitServiceObj->AddParameterChar   ('both',  1,  'INCHARB', 'var2', 'Z');
$param[] = $ToolkitServiceObj->AddParameterPackDec('both',  7,4,'INDEC1',  'var3', '001.0001');
$param[] = $ToolkitServiceObj->AddParameterPackDec('both', 12,2,'INDEC2',  'var4', '0000000003.04');
  $ds[] = $ToolkitServiceObj->AddParameterChar   ('both',  1,  'DSCHARA', 'ds1',  'A');
  $ds[] = $ToolkitServiceObj->AddParameterChar   ('both',  1,  'DSCHARB', 'ds2',  'B');
  $ds[] = $ToolkitServiceObj->AddParameterPackDec('both',  7,4,'DSDEC1',  'ds3',  '005.0007');
  $ds[] = $ToolkitServiceObj->AddParameterPackDec('both', 12,2,'DSDEC1',  'ds4',  '0000000006.08');
$param[] = $ToolkitServiceObj->AddDataStruct($ds);
$result  = $ToolkitServiceObj->PgmCall('ZZCALL', $testLib, $param, null, null);
echo "good so far ...\n";
var_dump($result);
  • PHP DB2 (without toolkit)
$fast = false;
$ipc = "*NA";
$ctl = "*here *cdata";
$xmlIn   = "<?xml version='1.0' encoding='ISO-8859-1'?>
<script>
<pgm name='ZZCALL' lib='ZENDSVR'>
<parm><data type='1a'>Y</data></parm>
<parm><data type='1a'>Z</data></parm>
<parm><data type='7p4'>001.0001</data></parm>
<parm><data type='12p2'>0000000003.04</data></parm>
<parm>
<ds>
  <data type='1a'>A</data>
  <data type='1a'>B</data>
  <data type='7p4'>005.0007</data>
  <data type='12p2'>0000000006.08</data>
</ds>
</parm>
</pgm>
</script>";
$xmlOut = '';
if ($fast) $conn = db2_pconnect($db, $user, $pass);     // persistent/pooled connection
else $conn = db2_connect($db, $user, $pass);            // full open/close connection
if (!$conn) die("Bad connect: $db, $user");
$stmt = db2_prepare($conn, "call $lib.$plug(?,?,?,?)"); // Call XMLSERVICE
                                                        // stored procedure interface
                                                        // in/out parameter (xmlOut)
                                                        // sizes: iPLUG4K - iPLUG15M
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);     // ? - /tmp/raw_$user (*sbmjob)
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);     // ? - *here or *sbmjob
$ret=db2_bind_param($stmt, 3, "xmlIn", DB2_PARAM_IN);   // ? - XML input script
$ret=db2_bind_param($stmt, 4, "xmlOut", DB2_PARAM_OUT); // ? - XML output return
$ret=db2_execute($stmt);
if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
var_dump($xmlOut);



PowerRuby DB2 (toolkit)
-----------------------
require 'active_record'
require 'xmlservice'

ActiveRecord::Base.establish_connection(
  :adapter   => 'ibm_db',
  :database => '*LOCAL',
  :username => 'MYUID',
  :password => 'MYPWD'
)
ActiveXMLService::Base.establish_connection(
  :connection => 'ActiveRecord',
  :install => "XMLSERVICE",
  :ctl => "*here *cdata",
  :ipc => "*NA",
  :size => 4096,
  :head => "<?xml version='1.0'?>"
)
zzcall = XMLService::I_PGM.new("zzcall","xmlservice")
zzcall << XMLService::I_a.new('inchara',1,'a')
zzcall << XMLService::I_a.new('incharb',1,'b')
zzcall << XMLService::I_p.new('indec1',7,4,11.1111)
zzcall << XMLService::I_p.new('indec2',12,2,222.22)
zzcall << XMLService::I_DS.new('inds1',1,
          [XMLService::I_a.new('dschara',1,'x'),
          XMLService::I_a.new('dscharb',1,'y'),
          XMLService::I_p.new('dsdec1',7,4,66.6666),
          XMLService::I_p.new('dsdec2',12,2,77777.77)])
zzcall.xmlservice
puts zzcall.out_xml
  • PowerRuby DB2 (without toolkit)
require 'active_record'

ipc = "*NA"
ctl = "*here *cdata"
xmlin = '<?xml version="1.0"?>
<script>
<pgm name="ZZCALL" lib="XMLSERVICE">
<parm  io="both">
  <data type="1A">a</data>
</parm>
<parm  io="both">
  <data type="1A">b</data>
</parm>
<parm  io="both">
  <data type="7p4">11.1111</data>
</parm>
<parm  io="both">
  <data type="12p2">222.22</data>
</parm>
<parm  io="both">
  <ds>
  <data type="1A">x</data>
  <data type="1A">y</data>
  <data type="7p4">66.6666</data>
  <data type="12p2">77777.77</data>
  </ds>
</parm>
<return>
  <data type="10i0">0</data>
</return>
</pgm>
</script>'
xmlout = ""
ActiveRecord::Base.establish_connection(
  :adapter   => 'ibm_db',
  :database => '*LOCAL',
  :username => 'MYUID',
  :password => 'MYPWD'
)
conn = ActiveRecord::Base.connection.connection
stmt = IBM_DB::prepare(conn, 'CALL XMLSERVICE.iPLUG512K(?,?,?,?)')
ret = IBM_DB::bind_param(stmt, 1, "ipc", IBM_DB::SQL_PARAM_INPUT)
ret = IBM_DB::bind_param(stmt, 2, "ctl", IBM_DB::SQL_PARAM_INPUT)
ret = IBM_DB::bind_param(stmt, 3, "xmlin", IBM_DB::SQL_PARAM_INPUT)
ret = IBM_DB::bind_param(stmt, 4, "xmlout", IBM_DB::SQL_PARAM_OUTPUT)
ret = IBM_DB::execute(stmt)
puts xmlout
  • Java DB2 (no toolkit)
import java.io.*;
import java.util.*;
import java.sql.*;
import java.math.*;

public class javaXMLserviceDemoJDBC {
public static void main(String[] args)   {
  Connection conn = null;
  Statement stmt=null;
  CallableStatement cstmt = null ;
  PreparedStatement pstmt = null;
  String url = "jdbc:db2://localhost"; // Set URL for data source
  String user = "MYUID";
  String password = "MYPWD";
  try
  { // Load the DB2(R) Universal JDBC Driver with DriverManager
    Class.forName("com.ibm.db2.jdbc.app.DB2Driver");
    conn = DriverManager.getConnection(url, user, password);
    String inputClob =
        "<?xml version='1.0'?>"
      + " <script>"
      + " <pgm name='ZZCALL' lib='XMLSERVICE'>"
      + " <parm><data type='1A'>a</data></parm>"
      + " <parm><data type='1A'>b</data></parm>"
      + " <parm><data type='7p4'>11.1111</data></parm>"
      + " <parm><data type='12p2'>222.22</data></parm>"
      + " <parm>"
      + " <ds>"
      + " <data type='1A'>x</data>"
      + " <data type='1A'>y</data>"
      + " <data type='7p4'>66.6666</data>"
      + " <data type='12p2'>77777.77</data>"
      + " </ds>"
      + " </parm>"
      + " <return><data type='10i0'>0</data></return>"
      + " </pgm>"
      + " </script>";
    String sql="CALL XMLSERVICE.iPLUG512K(?,?,?,?)";
    cstmt = conn.prepareCall(sql);
    System.out.println("Calling with valid name");
    cstmt.setString(1,"/tmp/packers01");
    cstmt.setString(2,"*sbmjob");
    cstmt.setString(3,inputClob);
    cstmt.registerOutParameter(4, Types.CLOB);
    cstmt.execute();
    String doc = cstmt.getString(4);
    System.out.println("****** Documento XML: **********");
    System.out.println(doc);
  }
  catch (Exception e)
  { System.out.println("******* Eccezione !!! *********");
    e.printStackTrace();
  }
}
}
  • perl DB2 (no toolkit)
use DBI;
use DBD::DB2::Constants;
use DBD::DB2;

$dbh = DBI->connect("dbi:DB2:*LOCAL")
            or die $DBI::errstr;

$stmt = 'call XMLSERVICE.iPLUG65K(?,?,?,?)';
$sth = $dbh->prepare($stmt)
  or die "prepare got error " . $dbh->err;
$ipc = "/tmp/perlme";
$sth->bind_param(1, $ipc)
  or die "bind 1 got error " . $dbh->err;
$ctl = "*sbmjob";
$sth->bind_param(2, $ctl)
  or die "bind 2 got error " . $dbh->err;
$xmlin = '<?xml version="1.0"?>
<script>
<pgm name="ZZCALL" lib="XMLSERVICE">
<parm  io="both">
  <data type="1A">a</data>
</parm>
<parm  io="both">
  <data type="1A">b</data>
</parm>
<parm  io="both">
  <data type="7p4">11.1111</data>
</parm>
<parm  io="both">
  <data type="12p2">222.22</data>
</parm>
<parm  io="both">
  <ds>
  <data type="1A">x</data>
  <data type="1A">y</data>
  <data type="7p4">66.6666</data>
  <data type="12p2">77777.77</data>
  </ds>
</parm>
<return>
  <data type="10i0">0</data>
</return>
</pgm>
</script>
';
$sth->bind_param(3, $xmlin)
  or die "bind 3 got error " . $dbh->err;
$xmlout = "";
$xmloutlen = 4096;
$sth->bind_param_inout(4, \$xmlout, $xmloutlen)
  or die "bind 4 got error " . $dbh->err;
$sth->execute()
  or die "execute got error" . $dbh->err;

XMLSERVICE REST interface

XMLSERVICE includes a simple REST interface (XMLCGI.PGM), we demonstrated using the REST service using only HTML/XML in a previous section. Most languages support REST calls, so XMLSERVICE REST interface can be very useful for cloud applications where DB2 drivers are not available. XMLSERVICE has REST production clients in most every scripting language you can imagine (PHP, Ruby, perl, python, etc.).

  • JavaScript REST (no toolkit)
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/dojo.xd.js" type="text/javascript"></script>
<script language="javascript">
dojo.require("dojox.xml.parser");
// you will need actual uid/pwd
// *NONE not enabled by default
var msgin = "xmlservice input";
var msgout = "xmlservice output";
var xmlhttp = null;
var url = encodeURI("http://"
        + self.location.hostname
        + "/cgi-bin/xmlcgi.pgm?"
        + "db2=*LOCAL"
        + "&uid=MYUID"
        + "&pwd=MYPWD"
        + "&ipc=/tmp/rangerhtmlonly"
        + "&ctl=*sbmjob"
        + "&xmlin="
        + "<?xml version='1.0'?>"
        + " <myscript>"
        + " <pgm name='ZZCALL' lib='XMLSERVICE'>"
        + " <parm var='p1'><data type='1A' var='d1'>a</data></parm>"
        + " <parm var='p2'><data type='1A' var='d2'>b</data></parm>"
        + " <parm var='p3'><data type='7p4' var='d3'>11.1111</data></parm>"
        + " <parm var='p4'><data type='12p2' var='d4'>222.22</data></parm>"
        + " <parm var='p5'>"
        + " <ds var='myds'>"
        + " <data type='1A' var='ds1'>x</data>"
        + " <data type='1A' var='ds2'>y</data>"
        + " <data type='7p4' var='ds3'>66.6666</data>"
        + " <data type='12p2' var='ds4'>77777.77</data>"
        + " </ds>"
        + " </parm>"
        + " <return var='rc'><data type='10i0' var='d1'>0</data></return>"
        + " </pgm>"
        + " </myscript>"
        + "&xmlout=32768");
function readNode(baseNode,output)
{ // @copy: http://blog.char95.com/post/simple-javascript-xml2array-parser/
  var node = baseNode.firstChild;
  if (output==undefined) var output = {};
  while(node)
  { var nodeData = {};
    if (node.attributes)
    { var nNodes = node.attributes.length;
      while(nNodes--) nodeData['$'+node.attributes[nNodes].nodeName] = node.attributes[nNodes].nodeValue;
    }
    if (output[node.nodeName]==undefined) output[node.nodeName] = new Array(nodeData);
    else output[node.nodeName].push(nodeData);
    var id = output[node.nodeName].length-1;
    output[node.nodeName][id] = readNode(node,output[node.nodeName][id]);
    if (node.firstChild) nodeData['#text'] = node.firstChild.nodeValue;
    node = node.nextSibling;
  }
  return output;
}
function processXMLSERVICE2()
{ var args =
  { url:url,
    handleAs:"xml",
    preventCache:true,
    load:function(data)
    { var xmlArray = readNode(data);
      var table = document.createElement('table');
      var output =
        "<th>parm</th>"
      + "<th>ds</th>"
      + "<th>var</th>"
      + "<th>value</th>\n";
      parms = xmlArray['myscript'][0]['pgm'][0]['parm'];
      for (var i=0;i<parms.length;i++)
      { if (i<parms.length - 1)
        { output += "<tr>"
          + "<td>" + parms[i].$var + "</td>\n"
          + "<td>" + "(na)" + "</td>\n"
          + "<td>" + parms[i]['data'][0].$var + "</td>\n"
          + "<td>" + parms[i]['data'][0]['#text'] + "</td>\n"
          + "</tr>";
        }
        else
        { dsvar = parms[i]['ds'][0].$var;
          dsdata = parms[i]['ds'][0]['data'];
          for (var j=0;j<dsdata.length;j++)
          { output += "<tr>"
            + "<td>" + parms[i].$var + "</td>\n"
            + "<td>" + dsvar + "</td>\n"
            + "<td>" + dsdata[j].$var + "</td>\n"
            + "<td>" + dsdata[j]['#text'] + "</td>\n"
            + "</tr>";
          }
        }
      }
      output += "</table>\n";
      table.setAttribute("border","1")
      table.innerHTML = output;
      document.getElementById("addtable").appendChild(table);
    },
    error:function(error)
    { alert("Error:" + error);
    }
  };
  var ajaxCall = dojo.xhrGet(args);
}
</script>
</head>
<body>
<p>This page demonstrates calling XMLSERVICE by JavaScript. Display source in your browser to see JavaScript used.</p>
<form>
<ul>
<li><a href="javascript: processXMLSERVICE2();">{XMLSERVICE JavaScript table (click me)}</a> - DoJo REST call RPG build table element</li>
</eul>
<p>
<div id="addtable"></div>
</p>
</form>
</body>
</html>
  • PHP REST (no toolkit)
<?php
function getxml() {
$clob = <<<ENDPROC
<?xml version='1.0'?>
<script>
<pgm name='ZZCALL' lib='XMLSERVICE'>
<parm><data type='1A'>a</data></parm>
<parm><data type='1A'>b</data></parm>
<parm><data type='7p4'>11.1111</data></parm>
<parm><data type='12p2'>222.22</data></parm>
<parm>
  <ds>
  <data type='1A'>x</data>
  <data type='1A'>y</data>
  <data type='7p4'>66.6666</data>
  <data type='12p2'>77777.77</data>
  </ds>
</parm>
<return><data type='10i0'>0</data></return>
</pgm>
</script>
ENDPROC;
return $clob;
}
// make the call
$i5persistentconnect = false;
$database = "*LOCAL";
$user = "MYUID";
$password = "MYPWD";
$libxmlservice = "XMLSERVICE";
$ipc = '/tmp/rangerhtmlonly';
$ctl = '*sbmjob';
$clobIn = getxml();
$clobOut = "";
$clobOutMax = "32000";
$i5rest = "http://174.79.32.155/cgi-bin/xmlcgi.pgm";
$postdata = http_build_query(
  array(
    'db2' => $database,
    'uid' => $user,
    'pwd' => $password,
    'ipc' => $ipc,
    'ctl' => $ctl,
    'xmlin' => $clobIn,
    'xmlout' => $clobOutMax    // size expected XML output
  )
  );
$opts = array('http' =>
  array(
    'method'  => 'POST',
    'header'  => 'Content-type: application/x-www-form-urlencoded',
    'content' => $postdata
  )
  );
$context  = stream_context_create($opts);
$clobOut = file_get_contents($i5rest, false, $context);
?>
  • PowerRuby REST (no toolkit)
require 'adapters/abstract_adapter'
require 'net/http'
require 'uri'

xmlin = '<?xml version="1.0"?>
<script>
<pgm name="ZZCALL" lib="XMLSERVICE">
<parm  io="both">
  <data type="1A">a</data>
</parm>
<parm  io="both">
  <data type="1A">b</data>
</parm>
<parm  io="both">
  <data type="7p4">11.1111</data>
</parm>
<parm  io="both">
  <data type="12p2">222.22</data>
</parm>
<parm  io="both">
  <ds>
  <data type="1A">x</data>
  <data type="1A">y</data>
  <data type="7p4">66.6666</data>
  <data type="12p2">77777.77</data>
  </ds>
</parm>
<return>
  <data type="10i0">0</data>
</return>
</pgm>
</script>'
xmlout = ""

post_args = {
:db2 => "*LOCAL",
:uid => "MYUID",
:pwd => "MYPWD",
:ipc => "*NA",
:ctl => "*here *cdata",
:xmlin => xmlin,
:xmlout => 4096
}
uri = URI("http://myibmi/cgi-bin/xmlcgi.pgm")
res = Net::HTTP.post_form(uri, post_args)
xmlout = res.body
puts xmlout
  • Java REST (no toolkit)
import java.net.*;
import java.io.*;
public class javaXMLserviceDemoREST {
  public static void main(String[] args)
  { try
    { // The URL class does not itself encode or decode any URL components according to the escaping mechanism defined in RFC2396.
      // It is the responsibility of the caller to encode any fields, which need to be escaped prior to calling URL,
      // and also to decode any escaped fields, that are returned from URL. Furthermore, because URL has no knowledge of URL escaping,
      // it does not recognise equivalence between the encoded or decoded form of the same URL
      String inputURL =
          "http://174.79.32.155/cgi-bin/xmlcgi.pgm?"
        + java.net.URLEncoder.encode(
          "db2=*LOCAL"
        + "&uid=MYUID"
        + "&pwd=MYPWD"
        + "&ipc=/tmp/rangerhtmlonly"
        + "&ctl=*sbmjob"
        + "&xmlin="
        + "<?xml version='1.0'?>"
        + " <script>"
        + " <pgm name='ZZCALL' lib='XMLSERVICE'>"
        + " <parm><data type='1A'>a</data></parm>"
        + " <parm><data type='1A'>b</data></parm>"
        + " <parm><data type='7p4'>11.1111</data></parm>"
        + " <parm><data type='12p2'>222.22</data></parm>"
        + " <parm>"
        + " <ds>"
        + " <data type='1A'>x</data>"
        + " <data type='1A'>y</data>"
        + " <data type='7p4'>66.6666</data>"
        + " <data type='12p2'>77777.77</data>"
        + " </ds>"
        + " </parm>"
        + " <return><data type='10i0'>0</data></return>"
        + " </pgm>"
        + " </script>"
        + "&xmlout=32768",
          "ISO-8859-1");
      // make REST call to XMLSERVICE
      URL restUrl = new URL(inputURL);
      URLConnection conn = restUrl.openConnection();
      // output processing
      System.out.println("Content type: " + conn.getContentType());
      System.out.println("Content length: " + conn.getContentLength());
      BufferedReader strm = new BufferedReader(new InputStreamReader(conn.getInputStream()));
      String inputLine;
      String doc = "";
      while ((inputLine = strm.readLine()) != null) {
      doc = doc + inputLine;
      }
      System.out.println("****** Documento XML: **********");
      System.out.println(doc);
    }
    catch (Exception e)
    { System.out.println("******* Eccezione !!! *********");
      e.printStackTrace();
    }
  }
}
``

RPG and XMLSERVICE

RPG is usually considered server side of XMLSERVICE, a called RPG PGM or SRVPGM (web service), but RPG is well versed in DB2, therefore can also be used as RPG client to XMLSERVICE. In fact, client RPG DB2 connection to XMLSERVICE will likely be fastest and easiest choice, especially for your remote IBM i systems (WRKRDBDIRE).

The RPG client examples will demonstrate RPG CGI using XMLSERVICE via Apache configuration, but the techniques also work as batch or green screen application, after using XMLSERVICE for a while, you may realize just how little code you have to write to do a great deal of work.

  • Apache CGI configuration (httpd.conf)
ScriptAlias /cgi-bin/ /QSYS.LIB/XMLSERVICE.LIB/
<Directory /QSYS.LIB/XMLSERVICE.LIB/>
  order allow,deny
  allow from all
  SetHandler cgi-script
  Options +ExecCGI
</Directory>
  • RPG client XMLSERVICE (*PGM)

RPG program ZZRPGSQL.PGM demonstrates client use of XMLSERVICE calling a *PGM (ZZCALL.pgm).

XMLSERVICE/ZZCALL (*PGM)
    D  INCHARA        S              1a
    D  INCHARB        S              1a
    D  INDEC1         S              7p 4
    D  INDEC2         S             12p 2
    D  INDS1          DS
    D   DSCHARA                      1a
    D   DSCHARB                      1a
    D   DSDEC1                       7p 4
    D   DSDEC2                      12p 2
      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
      * main(): Control flow
      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    C     *Entry        PLIST
    C                   PARM                    INCHARA
    C                   PARM                    INCHARB
    C                   PARM                    INDEC1
    C                   PARM                    INDEC2
    C                   PARM                    INDS1

The simple example is of course a bit contrived, simply extracting strings between <data>output</data> returned XMLSERVICE output, but serves to demonstrate XMLSERVICE call using Exec Sql call XMLSERVICE/iPLUG4K(:myIPC,:myCtl,:myXmlIn,:myXmlOut);.

ZZRPGSQL.PGM calling XMLSERVICE via Exec Sql call XMLSERVICE/iPLUG4K

H AlwNull(*UsrCtl)
H BNDDIR('QC2LE')

 * vars
D myIPC           s           1024A   inz(*BLANKS)
D myCtl           s           1024A   inz(*BLANKS)
D myXmlIn         s           4096A   inz(*BLANKS)
D myXmlOut        s           4096A   inz(*BLANKS)

D i               s             10i 0 inz(0)
D rn              s             10i 0 inz(0)

D data            s          65000A   inz(*BLANKS)
D xml1            s          65000A   inz(*BLANKS)
D xml2            s          65000A   inz(*BLANKS)
D xml3            s          65000A   inz(*BLANKS)
D xml4            s          65000A   inz(*BLANKS)

D strstr          PR              *   ExtProc('strstr')
D  pVal1                          *   Value options(*string)
D  pVal2                          *   Value options(*string)

D strlen          PR            10I 0 ExtProc('strlen')
D  pVal                           *   Value options(*string)

D writeIFS        PR            20I 0 ExtProc('write')
D   fd                          10I 0 value
D   buf                           *   value
D   size                        10I 0 value

D xmlfind         PR         65000A
D  xml                            *   value
D  nbr                          10i 0 value
D  xbeg                         32A   value
D  xbeg1                        32A   value
D  xend                         32A   value

D Main            PR                  ExtPgm('ZZRPGSQL')
 *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 * main(): Control flow
 *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
D Main            PI
 /free
  Monitor;

  // -----
  // input

  myIPC = '*NA';
  myCtl = '*here';
  myXmlIn =
    '<?xml version="1.0" encoding="ISO-8859-1"?>'                + x'0D'
  + '<script>'                                                   + x'0D'
  + '<pgm name="ZZCALL" lib="XMLSERVICE">'                       + x'0D'
  + '<parm><data type="1a" var="INCHARA">Y</data></parm>'        + x'0D'
  + '<parm><data type="1a" var="INCHARB">Z</data></parm>'        + x'0D'
  + '<parm><data type="7p4" var="INDEC1">001.0001</data></parm>' + x'0D'
  + '<parm><data type="12p2" var="INDEC2">0003.04</data></parm>' + x'0D'
  + '<parm>'                                                     + x'0D'
  + ' <ds>'                                                      + x'0D'
  + '  <data type="1a" var="DSCHARA">A</data>'                   + x'0D'
  + '  <data type="1a" var="DSCHARB">B</data>'                   + x'0D'
  + '  <data type="7p4" var="DSDEC1">005.0007</data>'            + x'0D'
  + '  <data type="12p2" var="DSDEC2">0000000006.08</data>'      + x'0D'
  + ' </ds>'                                                     + x'0D'
  + '</parm>'                                                    + x'0D'
  + '</pgm>'                                                     + x'0D'
  + '</script>'                                                  + x'00';
  myXmlOut = *BLANKS;

  // -----
  // sql call XMLSERVICE provided stored procedure(iPLUG4k -iPLUG15M)
  Exec Sql call XMLSERVICE/iPLUG4K(:myIPC,:myCtl,:myXmlIn,:myXmlOut);

  // -----
  // output (CGI)

  // -----
  // send header + end (LFLF)
  data = 'Content-type: text/html' + x'15' + x'15' + x'00';
  rn = writeIFS(1:%addr(data):strlen(%addr(data)));
  // -----
  // send return data
  // HTML table
  data  = '<h3>RPG call XMLSERVICE</h3>'                 + x'0D';
  data  = %trim(data) + '<table border="1">'             + x'0D';
  data  = %trim(data) + '<th>Parameter name</th>'        + x'0D';
  data  = %trim(data) + '<th>Type value</th>'            + x'0D';
  data  = %trim(data) + '<th>Input value</th>'           + x'0D';
  data  = %trim(data) + '<th>Output value</th>'          + x'0D';
  for i = 1 to 8;
    xml1  = xmlfind(%addr(myXmlIn): i:'var=' :'"':'"');
    xml2  = xmlfind(%addr(myXmlIn): i:'type=':'"':'"');
    xml3  = xmlfind(%addr(myXmlIn): i:'<data':'>':'</data>');
    xml4  = xmlfind(%addr(myXmlOut):i:'<data':'>':'</data>');
    // HTML table row
    data  = %trim(data) + '<tr>'                         + x'0D';
    data  = %trim(data) + '<td>' + %trim(xml1) + '</td>' + x'0D';
    data  = %trim(data) + '<td>' + %trim(xml2) + '</td>' + x'0D';
    data  = %trim(data) + '<td>' + %trim(xml3) + '</td>' + x'0D';
    data  = %trim(data) + '<td>' + %trim(xml4) + '</td>' + x'0D';
    data  = %trim(data) + '</tr>'                        + x'0D';
  endfor;
  data  = %trim(data) + '</table>'                       + x'00';
  rn = writeIFS(1:%addr(data):strlen(%addr(data)));

  On-error;
  Endmon;

  return;
 /end-free

 *****************************************************
 * xmlfind
 *****************************************************
P xmlfind         B
D xmlfind         PI         65000A
D  xml                            *   value
D  nbr                          10i 0 value
D  xbeg                         32A   value
D  xbeg1                        32A   value
D  xend                         32A   value
 * vars
D i               s             10i 0 inz(0)
D cbeg            s             33A   inz(*BLANKS)
D cbeg1           s             33A   inz(*BLANKS)
D cend            s             33A   inz(*BLANKS)
D pbeg            s               *   inz(*NULL)
D pend            s               *   inz(*NULL)
D xmlFragment     s          65000A   inz(*BLANKS)
 /free
  cbeg = %trim(xbeg) + x'00';
  cbeg1 = %trim(xbeg1) + x'00';
  cend = %trim(xend) + x'00';
  pbeg = xml;
  for i = 1 to nbr;
    // <item stuff>123</item>
    // x
    if pbeg <> *NULL;
      pbeg = strstr(pbeg + 1:%addr(cbeg));
    endif;
  endfor;
  // <item stuff>123</item>
  //            x
  if pbeg <> *NULL;
    pbeg = strstr(pbeg:%addr(cbeg1));
  endif;
  if pbeg <> *NULL;
    // <item stuff>123</item>
    //             x  x
    pbeg += 1;
    pend = strstr(pbeg:%addr(cend));
    if pend <> *NULL and pend > pbeg;
      xmlFragment = %str(pbeg:pend - pbeg);
    endif;
  endif;
  return xmlFragment;
 /end-free
P                 E

What is happening?

The flow of ZZRPGSQL.PGM is similar to HTML/XML, except we are using the XMLSERVICE provided stored procedure interface to call XMLSERVICE.

  • We point our browser to http://lp0364d:10022/cgi-bin/zzrpgsql.pgm, which is RPG CGI program ZZRPGSQL.PGM.
  • ZZRPGSQL.PGM uses XMLSERVICE DB2 interface Exec Sql call XMLSERVICE/iPLUG4K(:myIPC,:myCtl,:myXmlIn,:myXmlOut);, passing XML input request myXmlIn to XMLSERVICE.
  • XMLSERVICE.PGM parses XML input and dynamically loads/activates/calls target ZZCALL.PGM with included parameters <parm>...</parm>.
  • ZZCALL.PGM runs using normal in/out parameters (memory)
  • XMLSERVICE.PGM parses in/out parameters from ZZCALL.PGM into output XML document
  • ZZRPGSQL.PGM myXmlOut variable contains XML output document
  • ZZRPGSQL.PGM parses myXmlOut into a HTML table output (writeIFS)
  • browser sees the HTML table of ZZCALL data
  • RPG client XMLSERVICE (*SRVPGM)

RPG program ZZVRYSQL.PGM demonstrates client use of XMLSERVICE calling a *SRVPGM (ZZSRV.ZZVARY). As you read source code for ZZVRYSQL.PGM, you will see it is nearly identical to previous example ZZRPGSQL.PGM.

XMLSERVICE/ZZSRV.ZZVARY (*SRVPGM)
    P zzvary          B                   export
    D zzvary          PI            20A   varying
    D  myName                       10A   varying

XMLSERVICE called ZZSRV.ZZARRY SRVPGM demonstrates advance functions problematic for PCML based web services.

  • difficult – parameter 10A varying - PCML requires multiple XML definitions for length, data
  • impossible – return 20A varying - PCML has no ability to return complex data (only single integer)

ZZVRYSQL.PGM calling XMLSERVICE via Exec Sql call XMLSERVICE/iPLUG4K

H AlwNull(*UsrCtl)
H BNDDIR('QC2LE')

  * vars
D myIPC           s           1024A   inz(*BLANKS)
D myCtl           s           1024A   inz(*BLANKS)
D myXmlIn         s           4096A   inz(*BLANKS)
D myXmlOut        s           4096A   inz(*BLANKS)

D i               s             10i 0 inz(0)
D rn              s             10i 0 inz(0)

D data            s          65000A   inz(*BLANKS)
D xml1            s          65000A   inz(*BLANKS)
D xml2            s          65000A   inz(*BLANKS)
D xml3            s          65000A   inz(*BLANKS)
D xml4            s          65000A   inz(*BLANKS)

D strstr          PR              *   ExtProc('strstr')
D  pVal1                          *   Value options(*string)
D  pVal2                          *   Value options(*string)

D strlen          PR            10I 0 ExtProc('strlen')
D  pVal                           *   Value options(*string)

D writeIFS        PR            20I 0 ExtProc('write')
D   fd                          10I 0 value
D   buf                           *   value
D   size                        10I 0 value

D xmlfind         PR         65000A
D  xml                            *   value
D  nbr                          10i 0 value
D  xbeg                         32A   value
D  xbeg1                        32A   value
D  xend                         32A   value

D Main            PR                  ExtPgm('ZZVRYSQL')
  *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  * main(): Control flow
  *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
D Main            PI
  /free
  Monitor;

  // -----
  // input
  //     P zzvary          B                   export
  //     D zzvary          PI            20A   varying
  //     D  myName                       10A   varying
  myIPC = '*NA';
  myCtl = '*here';
  myXmlIn =
    '<?xml version="1.0" encoding="ISO-8859-1"?>'                + x'0D'
  + '<script>'                                                   + x'0D'
  + '<pgm name="ZZSRV" lib="XMLSERVICE" func="ZZVARY">'          + x'0D'
  + '<parm>'                                                     + x'0D'
  + '<data type="10a" var="myName" varying="on">Ranger</data>'   + x'0D'
  + '</parm>'                                                    + x'0D'
  + '<return>'                                                   + x'0D'
  + '<data var="retName" type="20A" varying="on">Mud</data>'     + x'0D'
  + '</return>'                                                  + x'0D'
  + '</pgm>'                                                     + x'0D'
  + '</script>'                                                  + x'00';
  myXmlOut = *BLANKS;

  // -----
  // sql call XMLSERVICE provided stored procedure(iPLUG4k -iPLUG15M)
  Exec Sql call XMLSERVICE/iPLUG4K(:myIPC,:myCtl,:myXmlIn,:myXmlOut);

  // -----
  // output (CGI)

  // -----
  // send header + end (LFLF)
  data = 'Content-type: text/html' + x'15' + x'15' + x'00';
  rn = writeIFS(1:%addr(data):strlen(%addr(data)));
  // -----
  // send return data
  // HTML table
  data  = '<h3>RPG call XMLSERVICE</h3>'                 + x'0D';
  data  = %trim(data) + '<table border="1">'             + x'0D';
  data  = %trim(data) + '<th>Parameter name</th>'        + x'0D';
  data  = %trim(data) + '<th>Type value</th>'            + x'0D';
  data  = %trim(data) + '<th>Input value</th>'           + x'0D';
  data  = %trim(data) + '<th>Output value</th>'          + x'0D';
  for i = 1 to 2;
    xml1  = xmlfind(%addr(myXmlIn): i:'var=' :'"':'"');
    xml2  = xmlfind(%addr(myXmlIn): i:'type=':'"':'"');
    xml3  = xmlfind(%addr(myXmlIn): i:'<data':'>':'</data>');
    xml4  = xmlfind(%addr(myXmlOut):i:'<data':'>':'</data>');
    // HTML table row
    data  = %trim(data) + '<tr>'                         + x'0D';
    data  = %trim(data) + '<td>' + %trim(xml1) + '</td>' + x'0D';
    data  = %trim(data) + '<td>' + %trim(xml2) + '</td>' + x'0D';
    data  = %trim(data) + '<td>' + %trim(xml3) + '</td>' + x'0D';
    data  = %trim(data) + '<td>' + %trim(xml4) + '</td>' + x'0D';
    data  = %trim(data) + '</tr>'                        + x'0D';
  endfor;
  data  = %trim(data) + '</table>'                       + x'00';
  rn = writeIFS(1:%addr(data):strlen(%addr(data)));

  On-error;
  Endmon;

  return;
  /end-free

  *****************************************************
  * xmlfind
  *****************************************************
P xmlfind         B
D xmlfind         PI         65000A
D  xml                            *   value
D  nbr                          10i 0 value
D  xbeg                         32A   value
D  xbeg1                        32A   value
D  xend                         32A   value
  * vars
D i               s             10i 0 inz(0)
D cbeg            s             33A   inz(*BLANKS)
D cbeg1           s             33A   inz(*BLANKS)
D cend            s             33A   inz(*BLANKS)
D pbeg            s               *   inz(*NULL)
D pend            s               *   inz(*NULL)
D xmlFragment     s          65000A   inz(*BLANKS)
  /free
  cbeg = %trim(xbeg) + x'00';
  cbeg1 = %trim(xbeg1) + x'00';
  cend = %trim(xend) + x'00';
  pbeg = xml;
  for i = 1 to nbr;
    // <item stuff>123</item>
    // x
    if pbeg <> *NULL;
      pbeg = strstr(pbeg + 1:%addr(cbeg));
    endif;
  endfor;
  // <item stuff>123</item>
  //            x
  if pbeg <> *NULL;
    pbeg = strstr(pbeg:%addr(cbeg1));
  endif;
  if pbeg <> *NULL;
    // <item stuff>123</item>
    //             x  x
    pbeg += 1;
    pend = strstr(pbeg:%addr(cend));
    if pend <> *NULL and pend > pbeg;
      xmlFragment = %str(pbeg:pend - pbeg);
    endif;
  endif;
  return xmlFragment;
  /end-free
P                 E

RPG client XMLSERVICE (DataQueue)

RPG program ZZQUESQL.PGM demonstrates client use of XMLSERVICE calling a CMDs and System APIs for Data Queue. As you read source code for ZZQUESQL.PGM, you will see it is nearly identical to previous example ZZRPGSQL.PGM.

DLTDTAQ DTAQ(XMLSERVICE/MYDATAQ)
CRTDTAQ DTAQ(XMLSERVICE/MYDATAQ) MAXLEN(100)
***************************************
* Send Data Queue (QSNDDTAQ) API
***************************************
* 1 Data queue name     Input Char(10)
* 2 Library name        Input Char(10)
* 3 Length of data      Input Packed(5,0)
* 4 Data Input Char(*)  Input
***************************************
* Receive Data Queue (QRCVDTAQ) API
***************************************
* 1 Data queue name  Input Char(10)
* 2 Library name     Input Char(10)
* 3 Length of data   Input Packed(5,0)
* 4 Data Char(*)     Output
* 5 Wait time        Input Packed(5,0)

ZZQUESQL.PGM calling XMLSERVICE via Exec Sql call XMLSERVICE/iPLUG4K

H AlwNull(*UsrCtl)
H BNDDIR('QC2LE')

 * vars
D myIPC           s           1024A   inz(*BLANKS)
D myCtl           s           1024A   inz(*BLANKS)
D myXmlIn         s           4096A   inz(*BLANKS)
D myXmlOut        s           4096A   inz(*BLANKS)

D i               s             10i 0 inz(0)
D rn              s             10i 0 inz(0)

D data            s          65000A   inz(*BLANKS)
D xml1            s          65000A   inz(*BLANKS)
D xml2            s          65000A   inz(*BLANKS)
D xml3            s          65000A   inz(*BLANKS)
D xml4            s          65000A   inz(*BLANKS)

D strstr          PR              *   ExtProc('strstr')
D  pVal1                          *   Value options(*string)
D  pVal2                          *   Value options(*string)

D strlen          PR            10I 0 ExtProc('strlen')
D  pVal                           *   Value options(*string)

D writeIFS        PR            20I 0 ExtProc('write')
D   fd                          10I 0 value
D   buf                           *   value
D   size                        10I 0 value

D xmlfind         PR         65000A
D  xml                            *   value
D  nbr                          10i 0 value
D  xbeg                         32A   value
D  xbeg1                        32A   value
D  xend                         32A   value

D Main            PR                  ExtPgm('ZZVRYSQL')
 *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 * main(): Control flow
 *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
D Main            PI
 /free
  Monitor;

  // -----
  // input
  // ***************************************
  // * Send Data Queue (QSNDDTAQ) API
  // ***************************************
  // * 1 Data queue name     Input Char(10)
  // * 2 Library name        Input Char(10)
  // * 3 Length of data      Input Packed(5,0)
  // * 4 Data Input Char(*)  Input
  // ***************************************
  // * Receive Data Queue (QRCVDTAQ) API
  // ***************************************
  // * 1 Data queue name  Input Char(10)
  // * 2 Library name     Input Char(10)
  // * 3 Length of data   Input Packed(5,0)
  // * 4 Data Char(*)     Output
  // * 5 Wait time        Input Packed(5,0)
  myIPC = '*NA';
  myCtl = '*here';
  myXmlIn =
    '<?xml version="1.0" encoding="ISO-8859-1"?>'                + x'0D'
  + '<script>'                                                   + x'0D'
  + '<cmd error="fast">DLTDTAQ DTAQ(XMLSERVICE/MYDATAQ)</cmd>'   + x'0D'
  + '<cmd error="fast">CRTDTAQ DTAQ(XMLSERVICE/MYDATAQ) MAXLEN(100)</cmd>'
  + x'0D'
  + '<pgm name="QSNDDTAQ">'                                      + x'0D'
  + ' <parm>'                                                    + x'0D'
  + '   <data type="10A" var="sndque">MYDATAQ</data>'            + x'0D'
  + ' </parm>'                                                   + x'0D'
  + ' <parm>'                                                    + x'0D'
  + '   <data type="10A" var="sndlib">XMLSERVICE</data>'         + x'0D'
  + ' </parm>'                                                   + x'0D'
  + ' <parm>'                                                    + x'0D'
  + '   <data type="5p0" var="sndlen">50</data>'                 + x'0D'
  + ' </parm>'                                                   + x'0D'
  + ' <parm>'                                                    + x'0D'
  + '   <data type="100A" var="snddata">i data queues</data>'    + x'0D'
  + ' </parm>'                                                   + x'0D'
  + '</pgm>'                                                     + x'0D'
  + '<pgm name="QRCVDTAQ">'                                      + x'0D'
  + ' <parm>'                                                    + x'0D'
  + '   <data type="10A" var="rcvque">MYDATAQ</data>'            + x'0D'
  + ' </parm>'                                                   + x'0D'
  + ' <parm>'                                                    + x'0D'
  + '   <data type="10A" var="rcvlib">XMLSERVICE</data>'         + x'0D'
  + ' </parm>'                                                   + x'0D'
  + ' <parm>'                                                    + x'0D'
  + '   <data type="5p0" var="rcvlen">50</data>'                 + x'0D'
  + ' </parm>'                                                   + x'0D'
  + ' <parm>'                                                    + x'0D'
  + '   <data type="100A" var="rcvdata">bad stuff</data>'        + x'0D'
  + ' </parm>'                                                   + x'0D'
  + ' <parm>'                                                    + x'0D'
  + '   <data type="5p0" var="rcvwait">0</data>'                 + x'0D'
  + ' </parm>'                                                   + x'0D'
  + '</pgm>'                                                     + x'0D'
  + '<cmd error="fast">DLTDTAQ DTAQ(XMLSERVICE/MYDATAQ)</cmd>'   + x'0D'
  + '</script>'                                                  + x'00';
  myXmlOut = *BLANKS;

  // -----
  // sql call XMLSERVICE provided stored procedure(iPLUG4k -iPLUG15M)
  Exec Sql call XMLSERVICE/iPLUG32K(:myIPC,:myCtl,:myXmlIn,:myXmlOut);

  // -----
  // output (CGI)

  // -----
  // send header + end (LFLF)
  data = 'Content-type: text/html' + x'15' + x'15' + x'00';
  rn = writeIFS(1:%addr(data):strlen(%addr(data)));
  // -----
  // send return data
  // HTML table
  data  = '<h3>RPG call XMLSERVICE</h3>'                 + x'0D';
  data  = %trim(data) + '<table border="1">'             + x'0D';
  data  = %trim(data) + '<th>Parameter name</th>'        + x'0D';
  data  = %trim(data) + '<th>Type value</th>'            + x'0D';
  data  = %trim(data) + '<th>Input value</th>'           + x'0D';
  data  = %trim(data) + '<th>Output value</th>'          + x'0D';
  for i = 1 to 9;
    xml1  = xmlfind(%addr(myXmlIn): i:'var=' :'"':'"');
    xml2  = xmlfind(%addr(myXmlIn): i:'type=':'"':'"');
    xml3  = xmlfind(%addr(myXmlIn): i:'<data':'>':'</data>');
    xml4  = xmlfind(%addr(myXmlOut):i:'<data':'>':'</data>');
    // HTML table row
    data  = %trim(data) + '<tr>'                         + x'0D';
    data  = %trim(data) + '<td>' + %trim(xml1) + '</td>' + x'0D';
    data  = %trim(data) + '<td>' + %trim(xml2) + '</td>' + x'0D';
    data  = %trim(data) + '<td>' + %trim(xml3) + '</td>' + x'0D';
    data  = %trim(data) + '<td>' + %trim(xml4) + '</td>' + x'0D';
    data  = %trim(data) + '</tr>'                        + x'0D';
  endfor;
  data  = %trim(data) + '</table>'                       + x'00';
  rn = writeIFS(1:%addr(data):strlen(%addr(data)));

  On-error;
  Endmon;

  return;
 /end-free

 *****************************************************
 * xmlfind
 *****************************************************
P xmlfind         B
D xmlfind         PI         65000A
D  xml                            *   value
D  nbr                          10i 0 value
D  xbeg                         32A   value
D  xbeg1                        32A   value
D  xend                         32A   value
 * vars
D i               s             10i 0 inz(0)
D cbeg            s             33A   inz(*BLANKS)
D cbeg1           s             33A   inz(*BLANKS)
D cend            s             33A   inz(*BLANKS)
D pbeg            s               *   inz(*NULL)
D pend            s               *   inz(*NULL)
D xmlFragment     s          65000A   inz(*BLANKS)
 /free
  cbeg = %trim(xbeg) + x'00';
  cbeg1 = %trim(xbeg1) + x'00';
  cend = %trim(xend) + x'00';
  pbeg = xml;
  for i = 1 to nbr;
    // <item stuff>123</item>
    // x
    if pbeg <> *NULL;
      pbeg = strstr(pbeg + 1:%addr(cbeg));
    endif;
  endfor;
  // <item stuff>123</item>
  //            x
  if pbeg <> *NULL;
    pbeg = strstr(pbeg:%addr(cbeg1));
  endif;
  if pbeg <> *NULL;
    // <item stuff>123</item>
    //             x  x
    pbeg += 1;
    pend = strstr(pbeg:%addr(cend));
    if pend <> *NULL and pend > pbeg;
      xmlFragment = %str(pbeg:pend - pbeg);
    endif;
  endif;
  return xmlFragment;
 /end-free
P                 E

XMLSERVICE Functions

Goto Main Page

One sample worth 1,000 words

  • XMLSERVICE can handle multiple requests in a single XML input document.

  • XMLSERVICE works with standard https://, wherein, “secure” as any other web facility SSL encrypted (actual user profile, see xmlcgi.pgm).

  • XMLSERVICE works with Basic Auth, etc., because XMLSERVICE is just another RPG CGI (*NONE user/pwd, see crtnone.clp/xmlnone.pgm).

  • XMLSERVICE REST example is simple html/xml, but any language will do, with/without a language toolkit (Common lab):

    <form id="myForm" name="myForm" action="https://common1.frankeni.com:47700/cgi-bin/xmlcgi.pgm" method="post">
    <input type="hidden" name="db2" value="*LOCAL">
    <br>User: <input type="input" name="uid" value=""> (see instructor)
    <br>Password: <input type="password" name="pwd" value="">
    <input type="hidden" name="ipc" value="*na">
    <input type="hidden" name="ctl" value="*here">
    <br>XML Input:
    <br><textarea readonly name="xmlin" rows="20" cols="100">
    <?xml version='1.0'?>
    <xmlservice>
    <cmd>CHGLIBL LIBL(XMLSERVICE) CURLIB(XMLSERVICE)</cmd>
    <pgm name='ZZCALL'>
    <parm><data type='1A'>a</data></parm>
    <parm><data type='1A'>b</data></parm>
    <parm><data type='7p4'>11.1111</data></parm>
    <parm><data type='12p2'>222.22</data></parm>
    <parm>
      <ds>
      <data type='1A'>x</data>
      <data type='1A'>y</data>
      <data type='7p4'>66.6666</data>
      <data type='12p2'>77777.77</data>
      </ds>
    </parm>
    </pgm>
    <sql>
    <query>select * from QIWS.QCUSTCDT where LSTNAM='Jones'</query>
    <fetch block='all'/>
    </sql>
    </xmlservice>
    </textarea>
    <input type="hidden" name="xmlout" value="512000">
    <br><input type="submit" name=submit" value="submit" />
    </form>
    </body>
    </html>
    

XMLSERVICE interfaces (with download)

XMLSERVICE includes two basic interfaces, REST (Apache), and, database (DB2). Either interface receives XML input and returns XML output. The samples below show XMLSERVICE library (download/test library), but multiple language products ship XMLSERVICE in different libraries, so adjust your LIB accordingly (PHP - ZENDSVR/ZENDSVR6, Ruby-POWERRUBY, etc.). XMLSERVICE uses hard coded library schemes (PLUGCONF), to avoid interfering with user set library lists, therefore, you can only SAV/RST XMLSERVICE to the original library. If you move XMLSERVICE non-originating libraries it will fail at runtime.

  • REST interface (xmlcgi.pgm)
http://myibmi/cgi-bin/xmlcgi.pgm?db2=x&uid=x&pwd=x&ipc=x&ctl=x&xmlin=x&xmlout=x&persis=x
    db2 - what database (*LOCAL tested)
    uid - user profile (*NONE - no uid version 1.5+)
    pwd - profile password (*NONE - no password version 1.5+)
    ipc - IPC key name/security route to XMLSERVICE job (/tmp/fred01, etc.)
    ctl - CTL admin control XMLSERVICE job (see control below)
    xmlin - XML input document (request)
    xmlout - expected size of XML output document (response size in bytes)
    optional:
      persis - name persistent DB2 connection 8 chars or less (not often used)

Configure (LIB match actual product, php, ruby, etc.):
/www/myinstance/conf/httpd.conf
ScriptAlias /cgi-bin/ /QSYS.LIB/XMLSERVICE.LIB/
<Directory /QSYS.LIB/XMLSERVICE.LIB/>
  AllowOverride None
  order allow,deny
  allow from all
  SetHandler cgi-script
  Options +ExecCGI
</Directory>
  • DB2 stored procedure interface (lib/iPLUGxxx - xmlstoredp.srvpgm)
call XMLSERVICE.iPLUG512K(ipc, ctl, xmlin [, xmlout])
    IN IPC CHAR(1024) - IPC key name/security
    IN CTL CHAR(1024) - CTL admin control XMLSERVICE job
    IN CI CLOB(15M) - XML input document (request)
    OUT CO CLOB(15M) - XML output document (response)
        Note: iPLUGRxxx procedures return a result set that is collected by fetch.

call XMLSERVICE.iPLUG512K(ipc, ctl, xmlin xmlout)
---
XMLSERVICE.iPLUG4K(IN IPC CHAR(1024), IN CTL CHAR(1024),IN CI CHAR(4064), OUT C0 CHAR(4064))
XMLSERVICE.iPLUG32K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(32000), OUT CO CLOB(32000))
XMLSERVICE.iPLUG65K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(65K), OUT CO CLOB(65K))
XMLSERVICE.iPLUG512K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(512K), OUT CO CLOB(512K))
XMLSERVICE.iPLUG1M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(1M), OUT CO CLOB(1M))
XMLSERVICE.iPLUG5M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(5M), OUT CO CLOB(5M))
XMLSERVICE.iPLUG10M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(10M), OUT CO CLOB(10M))
XMLSERVICE.iPLUG15M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(15M), OUT CO CLOB(15M))

stmt = call XMLSERVICE.iPLUG512K(ipc, ctl, xmlin)
while (row = fetch(stmt)) xmlout += row[0]
---
XMLSERVICE.iPLUGR4K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CHAR(4096))
XMLSERVICE.iPLUGR32K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CHAR(32000))
XMLSERVICE.iPLUGR65K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(65K))
XMLSERVICE.iPLUGR512K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(512K))
XMLSERVICE.iPLUGR1M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(1M))
XMLSERVICE.iPLUGR5M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(5M))
XMLSERVICE.iPLUGR10M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(10M))
XMLSERVICE.iPLUGR15M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(15M))

Enable DB2 drivers with no LOB support (JTOpen lite enabler - 1.9.0+):
XMLSERVICE.iPLUGRC32K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI VARCHAR(32700), IN CNT INTEGER)
Note iPLUGRC32K:
  XML input VARCHAR(32700) can be a limit, therefore interface allows
  accumulated sends of partial XML document. Any call with non-zero
  counter (CNT > 0), XMLSERVICE assumes to be XML document partial
  accumulation. When counter reaches zero (CNT = 0),
  XMLSERVICE will process the request.

XMLSERVICE Control words

Control (CTL) keywords for operator control over XMLSERVICE jobs.

  • stateless connection – xmlservice here ($ctl="*here";) – run in QSQSRVR job, db2_connect current profile control. *here overrides/ignores ipc, runs “*here” in the QSQSRVR job.
  • private connection 1 – xmlservice spawn ($ctl=""; $ipc="/tmp/user";) – inherit parent job attributes, include QSQ job profile, and, job description. Per manual (below), xmlservice spawn server job has always been default when ipc included (internalKey), but missing *sbmjob.
    • Change: 1.9.3 - Rod asked QSQSRVR name changed on spawn (spawn child name also QSQSRVR), therefore, new name spawn child will be XMLSERVICE (XMLSERVICE PGM-XMLSERVICE).
  • private connection 2 – xmlservice sbmjob ($ctl="*sbmjob"; $ipc="/tmp/user";) – control sbmjob, job description, etc. *sbmjob allows control of job description for xmlservice server job.
    • Alternative *sbmjob, xmlservice allows full control via <sbmjob>SBMJOB CMD(CALL PGM(XMLSERVICE/XMLSERVICE) PARM('/tmp/user') ... other sbmjob parms ...)</sbmjob> (XTOOLKIT PGM-XMLSERVICE).
*****************************************************
* IPC control options:
*----------------------------------------------------
*--- very high peformance ignore all flag parsing (loop calls, etc.)
* *ignore
*    - do not parse flags (high performance)
*      example: $ctl="*ignore";
*----------------------------------------------------
*--- kill XMLSERVICE job
* *immed
*    - end server immed destroy IPC
*      example: $ctl="*immed";
*----------------------------------------------------
*--- misc functions XMLSERVICE
* *license
*    - return license for this code
* *session
*    - retrieve session key (IPC name /tmp/fred042)
* *clear
*    - clear internal XMLSERVICE caches,
*                       but will not deactivate loaded PGM/SRVPGM, etc.
*----------------------------------------------------
* -- if you need to fix result set return drivers (iPLUGRxxx)
*    hack for pesky DB2 drivers adding "junk" to back of records.
* *hack
*    - add </hack> each record of a result set
*      example: $ctl="*hack";
*      - iPLUGRxxx XMLSERVICE stored procedures
*        return result sets of 3000 byte records,
*        you must loop fetch and concat records
*        to recreate output XML ... except
*        some DB2 drivers have junk end ...
*      - enable easy rec truncate ill behaved drivers
*        remove all past </hack> during concat
*        loop fetch records 1-n
*        rec1: <script>....</hack> (3000 bytes)
*        rec2: ............</hack> (3000 bytes)
*        recn: ...</script></hack> (<3000 bytes)
*----------------------------------------------------
* -- pause XMLSERVICE job(s) for debugger attach (message to qsysopr)
* *debug
*    - stop call server with message qsysopr (XMLSERVICE)
*      example: $ctl="*debug";
* *debugproc
*    - stop stored proc with message qsysopr (client QSQSRVR)
* *debugcgi
*    - stop CGI with message qsysopr         (XMLCGI only)
* *test[(n)]
*    - test parse XML in/out and report n level information
*----------------------------------------------------
* -- override default XMLSERVICE client/server spawn child behaviour
* *sbmjob[(lib/jobd/job/asp)]
*    - sbmjob job (instead of XMLSERVICE default spawn)
*      example: $ctl="*sbmjob";
*      example: $ctl="*sbmjob(QSYS/QSRVJOB/XTOOLKIT)";
*      example: $ctl="*sbmjob(ZENDSVR/ZSVR_JOBD/XTOOLKIT)";
*      - default values provided plugconf.rpgle
*      - optional asp INLASPGRP(ASP1) (added 1.6.5)
*      -- Notes:
*         - See embedded XML overrides for user full control
*           of XMLSERVICE start behavior SBMJOB settings
* *here
*    - run stateless in stored proc job (client only job)
*      example: $ctl="*here";
*      - commonly known as running in PHP job, but in fact
*        more likely runs in database job you connected
*        on/off machine DRDA/ODBC/PASE (QSQSRVR, etc.)
*      - generally runs slower using "one process"
*        because XMLSERVICE has to restart itself,
*        wake up PASE, find/load your PGM call, etc.
* *nostart
*    - disallow spawn and sbmjob (web not start anything)
*      example: $ctl="*nostart";
*      - probably prestart all your XMLSERVICE jobs
*        SBMJOB CMD(CALL PGM(XMLSERVICE/XMLSERVICE)
*        PARM('/tmp/db2ipc042')) USER(DB2)
*      - consider using a custom plugconf to disable
*        issues with timeout defaults (*idle/*wait)
* *java (1.9.2)
*    - start JVM allowing user classpath
*      <cmd>ADDENVVAR ENVVAR(CLASSPATH) VALUE('$ours')
*           REPLACE(*YES)</cmd>
*      <pgm>... calling my RPG w/JAVA ... </pgm>
* *sqljava or *dbgjava (port 30000) (1.9.2)
*    - start JVM allowing DB2 classpath (no user control)
*       SQLJ.INSTALL_JAR into schema
*       /QIBM/UserData/OS400/SQLLib/Function/jar/(schema)
*----------------------------------------------------
* -- CDATA xml output
* *cdata(on|off)
*    - xml data fields returned with cdata
*      <data><![CDATA[...]]></data>
*      - on - CDATA (default 1.6.2)
*      - off - CDATA off
* -- escape xml reserved chars
* *escp
*    5 chars will be escaped in the output
*       &  --> &amp;
*       >  --> &gt;
*       <  --> &lt;
*       '  --> &apos;
*       "  --> &quot;
* -- special hex encoded XML document
* *hex(in/out/both)
*    - xml document hex encoded avoiding conversions
*      - in   - HEX convert input side only
*      - out  - HEX convert all output
*      - both - HEX convert input and output
*    example (php sudo):
*      $doc = "<?xml  version='1.0'> ... </script>";
*      $hexin  = bin2hex($doc);     // doc-2-hexin
*      XMLSERVICE($ipc, $ctl="*hex", $hexin, $hexout);
*      $doc = pack("H*", $hexout);  // hexout-2-doc
*     -- Notes
*      - hex string is '0123456789ABCDEF' or '...abcdef'
*        which are CCSID immutable (or upper most always)
*        which will arrive at XMLSERVCE in ebcdic via
*        "natural" clob/rest interface transport conversion
*        (ie., iBLOBxxx would not work *hex, arrive ascii)
*        YES (clob): ebcdic (x'F0F1...C6') or (x'...86')
*        NO  (blob):  ascii  (x'3031...46') or (x'...66')
*      - hex option is useful with CCSID below
*        with following order of operations:
*        - input : *hex followed by *before
*        - output: *after followed by *hex
* -- CCSID conversion XMLSERVICE (1.6.2)
* *before(CCSIDFrom/CCSIDTo[/action])
*    - conversion before calling XML processing
*      - CCSIDFrom - XML document client CCSID
*      - CCSIDTo   - XML document XMLSERVICE CCSID
*      - action
*        call   - call XMLSERVICE (default)
*        nocall - convert only (no call)
* *after(CCSIDFrom/CCSIDTo)
*    - conversion after calling XML processing
*      - CCSIDFrom - XML document XMLSERVICE CCSID
*      - CCSIDTo   - XML document client CCSID
* *pase(CCSIDpase/CCSIDile)
*    - conversion PGM, LIB, etc, names call processing
*      - CCSIDpase - name CCSID PASE side (ascii)
*      - CCSIDile  - name CCSID ILE side (ebcdic)
*
*    example: $ctl="*before(819/37) *after(37/819)";
*      - DB2 interface provides many possibilities
*        iPLUGxxx - original CLOB automatic DB2 converts
*        iBLOBxxx - raw binary no DB2 converts (1.6.2)
*      - action='nocall' is used to convert
*        anything on IBM i and send it back
*        without calling XMLSERVICE
*      - DB2 interface conversion effects are
*        virtually unlimited performed on IBM i
*        avoiding additional "code tables" on client
*      - *pase default (should work)
*        ILE ccsid 0 means job CCSID (all ebcdic)
*        PASE csid default Qp2paseCCSID (see API)
*----------------------------------------------------
* -- server time out jobs XMLSERVICE (1.6.2)
* *wait[(seconds[/action])]
*    - client side wait for XMLSERVICE call (client side)
*      example: $ctl="*wait(10)";
*      - default action *wait(60/busy) (see plugconfx)
* *call[(seconds[/action[/duration[/job]]])]
*    - client/server side XMLSERVICE call wait (PGM, SRVPGM, PASE, etc)
*      example: $ctl="*wait(10) *call(5/busy/client)";
*      - default for both client/server is *call(0)
*        means wait on call forever (user code flawless),
*        but can be overriden client/server/both
* *idle[(seconds[/action[/duration]])]
*    - server side XMLSERVICE idle no activity
*      example: $ctl="*wait(10/kill) *call(30/kill) *idle(30/kill/perm)";
*      - default action *idle(1800/kill) (see plugconfx)
*    -- time out parameters
*      seconds:
*        -1 - current default timer
*         0 - no timer, no timeout, wait forever
*         n - idle timer "pop" seconds
*      action:
*        kill - end job immed
*        user - user override signal behaviour (see plugconfx)
*        busy - return busy XML (client side)
*               busy response (1301050):
*               <error>
*               <errnoxml>1301050</errnoxml>
*               <xmlerrmsg>IPC timeout busy</xmlerrmsg>
*               </error>
*      duration:
*        perm - set and use new defaults all requests
*        orig - reset and use original compile defaults (see plugconfx)
*      job:
*        client - *call action applies client side
*        server - *call action applies server side
*    -- Notes:
*      - default timeout/action provided plugconf.rpgle,
*        but each request may override/reset to fit task(s)
*      - signal SIGALRM used with this function
*        can affect user program calls,
*        *call(0) may be used to turn off timer
*        during user program calls
*      - action 'user' allows for custom signal
*        processing in the RPG code (see plugconfx)
*      - if duration not specified, attributes
*        *wait(),*call(),*idle() are temporary
*        for this call only and return to last defaults.
*      - if 'job' not specified on *call(),
*        attribute settings apply to both sides
*      - end job immed kills XMLSERVICE job (server)
*        and destroys IPC, so any waiting client is
*        released with an IPC missing error.
*----------------------------------------------------
* -- batch XMLSERVICE processing (version 1.6.2)
* *batch
*    - use a free batch slot 1 - 16 (release client)
*      responses:
*        <id status='set'>1-16</id> - set batch processing
*        <id status='full'>0</id>   - no slots available
*      example: $ctl="*sbmjob *batch";
*      - *batch holds output XML memory until retrieved
*      - *get(n) retrieve of XML memory (some time later)
*      - *batch releases XMLSERVICE client (caller), and
*        returns batch slot number (n) assigned for work.
* *get[(n)]
*    - get XML results from batch slot 1 - 16 (release slot)
*      responses (report not available):
*        <id status='small'>1-16</id> - buffer too small
*        <id status='done'>1-16</id>  - complete removed
*      example: $ctl="*get";
*      example: $ctl="*get(3)";
*      - use with *wait(sec/busy) to avoid hang during
*        *batch running (do something else while waiting)
*      - *get without slot number will get one result
*        any completed batch slot
*      - *get(3) will only get result of batch slot 3
*----------------------------------------------------
* -- flight data options (affects performance, state of disrepair) --
* *rpt
*    - performance report last call
* *fly
*    - flight record performance
* *nofly
*    - no flight record performance (default)
* *justproc
*    - call stored proc, get into XMLSERVICE client,
*      do nothing while in XMLSERVICE, return back
*      - used to check transport speed only
* -- log to database file  (1.7.1) --
* *log[(key)]
*    - log records into database
* *nolog
*    - no log records into database (default)
*   Note:
*   - *log key is unique allowing both PHP and XMLSERVICE
*     to record event log data and produce queries of collected
*     reports.
*     Log file layout:
*     create table XMLSERVLOG/LOG (
*       key varchar(64) NOT NULL WITH DEFAULT,
*       log TIMESTAMP NOT NULL WITH DEFAULT,
*       text varchar(64) NOT NULL WITH DEFAULT)
*     Supplemental log dump XML data layout:
*     create table XMLSERVLOG/DUMP (
*       key varchar(64) NOT NULL WITH DEFAULT,
*       log TIMESTAMP NOT NULL WITH DEFAULT,
*       text clob(15M) NOT NULL WITH DEFAULT)
*  - programers/vendors can alter xmlservice log database
*    with plugconf (or custom)
*****************************************************
* embedded XML control overrides and function
*****************************************************
* sbmjob full user override of SBMJOB for XMLSERVICE start-up  -- (1.7.0+)
*  <sbmjob>SBMJOB CMD(CALL PGM(ZENDSVR/XMLSERVICE) PARM('/tmp/override'))</sbmjob>
*  Where SBMJOB can be any user settings (cut/paste green screen command) ...
*   ... required parameters for for XMLSERVICE to start CALL + PARM
*   example:
*       CMD(CALL PGM(ZENDSVR/XMLSERVICE) PARM('/tmp/xxxxxx')) <-- xmlservice test lib
*       -- or --
*       CMD(CALL PGM(ZENDSVR/XMLSERVICE) PARM('/tmp/xxxxxx')) <-- Zend Server production lib
*       -- all other sbmjob parms at your control --
*   Note:
*   - SBMJOB full control allows user to set any type of LIBL,
*     or SBMJOB options, or even custom PGM to call XMLSERVICE
*****************************************************
* start/use/stop exclusive use shared IPC (hotel reservation) -- (1.6.8+)
*  <start>KEY</start>  - acquire IPC -- first request
*  <use>KEY</use>      - match IPC   -- each request
*  <stop>KEY</stop>    - release IPC -- last request
*  Where KEY anything managed by user (key-2-IPC session data) ...
*   ... random based key -- scaling open any users,
*       want benefit of private RPG call (open files, etc),
*       do not care about reservation multi-request transaction
*       -> hybrid stateless/private call,
*          hold IPC for life of script only and release,
*          but limit jobs
*   ... user based key -- scaling come/go users,
*       want benefit of private RPG call (open files, etc),
*       also want reservation transactions
*       -> hybrid persistent/private,
*          transaction across multi-request (browser clicks),
*          but limited jobs
*   ... task based key -- everyone uses same task/tasks
*       limited pool jobs and all must wait a turn
*       -> hybrid private/persistent with pre-start pool,
*          transaction across multi-request (browser clicks),
*          load balancing design to limit machine stress
*   example many requests exclusive use IPC
*    -- no time out --
*     $ctl .= " *idle(0)"
*    -- request 1 --
*     <?xml version="1.0"?>
*     <script>
*     <start>unique-user-key</start>
*     </script>
*    -- request 2 (two minutes later) --
*     <?xml version="1.0"?>
*     <script>
*     <use>unique-user-key</use>
*     <cmd exec='rexx'>RTVJOBA USRLIBL(?)</cmd>
*     </script>
*    -- request 3 (1/2 hour later) --
*     <?xml version="1.0"?>
*     <script>
*     <use>unique-user-key</use>
*     <pgm name='ZZCALL'>
*      <parm>
*       <data type='1A'>a</data>
*      </parm>
*      <return>
*       <data type='10i0'>0</data>
*      </return>
*     </pgm>
*     </script>
*    -- request n (2 hours later) --
*     <?xml version="1.0"?>
*     <script>
*     <stop>unique-user-key</stop>
*     </script>
*   Note:
*   - <start>unique-user-key</start>
*     acquire exclusive IPC if available,
*   - <use>unique-user-key</use>
*     must appear XML every request
*     job held forever until see <stop>
*   - <stop>unique-user-key</stop>
*     release IPC for any other use
*   - <start>no-match-key</start>
*     or <use>unique-user-key</use>
*     non-matching key results in error
*     almost instantly (no wait)
*       busy response (1301060):
*         <error>
*         <errnoxml>1301060</errnoxml>
*         <xmlerrmsg>IPC owner busy</xmlerrmsg>
*         </error>
*   - thoughtful setting server idle timeout
*     can control unwanted reservation hangs
*     due to careless users or errors
*     $ctl .= " *idle(60)"
*************************************************************************

XMLSERVICE call CMD

Syntax can largely be cut/paste from a 5250 command line.

*************************************************************************
* 1) call i CMD
* XMLSERVICE allows calls of *CMDS on IBM i. Typically, you cut/paste
* from a 5250 QCMD line using prompt (F4). You may use choose the utility
* to run your command with attribute 'exec'. However, *CMDS with
* in/out parameters, like RTVJOBA., you must use 'exec'='rexx'.
* ---
* <cmd [exec='cmd|system|rexx'
*       hex='on'
*       before='cc1/cc2/cc3/cc4'
*       after='cc4/cc3/cc2/cc1'
*       error='on|off|fast'
*       ]>values (see IBM i *CMD)</cmd>
* ---
* cmd         - command tag
*  values     - (see IBM i *CMD IBM i- 5250 cut/paste)
* options
*  exec
*     cmd     - qcmdexe only return true/false (default)
*     system  - system utility return CPFxxxx
*     rexx    - rexx output parms and return CPFxxxx
*                 (?) character type
*                 (?N) explicit cast numeric
*  hex (1.6.8)
*     on      - input character hex (5147504C20202020)
*  before
*     cc(n)   - input ccsid1->ccsid2->ccsid3->ccsid4
*  after
*     cc(n)   - output ccsid1->ccsid2->ccsid3->ccsid4
*  error (1.7.6)
*     on      - script stops, full error report
*     off     - script continues, job error log (default)
*     fast    - script continues, brief error log
* ---
* example run command (original)
*  <?xml version="1.0"?>
*  <xmlservice>
*  <cmd>ADDLIBLE LIB(DB2) POSITION(*FIRST)</cmd>
*  </xmlservice>
* ---
* example output command (exec='rexx')
*  <?xml version='1.0'?>
*  <xmlservice>
*  <cmd exec='rexx'>RTVJOBA USRLIBL(?) SYSLIBL(?)</cmd>
*  <cmd exec='rexx'>RTVJOBA CCSID(?N) OUTQ(?)</cmd>
*  <cmd exec='rexx'>RTVSYSVAL SYSVAL(QDATETIME) RTNVAR(?)</cmd>
*  </xmlservice>
* ---
*   Note:
*   - <cmd>command</cmd> should be all on one line (no LFs)
*   - <cmd> run in XMLSERVICE job.
*     cmd    - qcmdexe only return true/false       (default)
*     system - system utility return CPFxxxx        (1.5.2)
*              <cmd exec='system'><error>CPF2103</error></cmd>
*     rexx   - rexx output parms and return CPFxxxx (1.5.2)
*              <cmd exec='rexx'><error>CPF2103</error></cmd>
*   - exec='rexx'
*     All parms are assume to be character unless
*     (?N) to explicit cast to numeric (rtvjoba). Most
*     RTVxxxx that ask for a CL variable RTNVAR will
*     not require the (?N) cast (IBM i manuals).
*     QTEMP/XMLREXX(HOW) is created on demand
*     by RPG module plugile (Github download).
*     QTEMP/OUTREXX(OUTREXX) is created for
*     command temp data between RPG and REXX.
*   - Up to four conversions can take place
*     for the truly complex ccsid issues (1.6.8)
*     <cmd hex='on' before='cc1/cc2/cc3/cc4' after='cc4/cc3/cc2/cc1'>
*       flow:
*       -> PHP client bin2hex('wild_ascii_raw_chars')
*       -> xmlservice hex2bin back to 'wild_ascii_raw_chars'
*       -> xmlservice convert cc1->cc2->cc3->cc4 (before)
*       -> xmlservice make ILE call
*       -> xmlservice convert cc4->cc3->cc2->cc1 (after)
*       -> xmlservice tohex "xml_hex_back"
*       -> PHP client $chars = pack('H*',"xml_hex_back")
*       output (incompatible change hex/ccsid 1.7.4+):
*       <cmd exec='rexx' hex='on' before='819/37' after='37/819'>
*         <success><![CDATA[+++ success RTVJOBA USRLIBL(?) SYSLIBL(?)]]></success>
*         <row><data desc='USRLIBL'><hex><![CDATA[5147504C20202020202020]]></hex></data></row>
*         <row><data desc='SYSLIBL'><hex><![CDATA[5153595320202020202020]]></hex></data></row>
*       </cmd>
*   - error='on,off,fast' (1.7.6)
*       on  - script stops, full error report
*       off - script continues, job error log (default)
*       fast - script continues, brief error log
*************************************************************************

XMLSERVICE call PASE

Syntax can mostly be cut/paste from PASE shell (call qp2term).

*************************************************************************
* 2) call PASE utility
* XMLSERVICE allows calls of PASE utilities on IBM i. Typically, you cut/paste
* from a PASE command line (call qp2term). PASE shell 'sh' is used for
* execution of your utilities, which, is default behavior of PASE popen() API.
* ---
* <sh [rows='on|off'
*      hex='on'
*      before='cc1/cc2/cc3/cc4'
*      after='cc4/cc3/cc2/cc1'
*      error='on|off|fast'
*      ]>values (see PASE utility)</sh>
* ---
* sh          - shell tag
*  values     - (see PASE utility - call qp2term cut/paste)
* options
*  rows
*     on      - return rows lines
*     off     - return one string (default)
*  hex (1.7.4)
*     on      - input character hex (5147504C20202020)
*  before
*     cc(n)   - input ccsid1->ccsid2->ccsid3->ccsid4
*  after
*     cc(n)   - output ccsid1->ccsid2->ccsid3->ccsid4
* ---
*  error (1.7.6)
*     on      - script stops, full error report
*     off     - script continues, job error log (default)
*     fast    - script continues, brief error log
* ---
* example run PASE shell
*  <?xml version="1.0"?>
*  <xmlservice>
*  <sh rows='on'>/QOpenSys/usr/bin/system 'wrkactjob' | grep -i fr</sh>
*  </xmlservice>
* ---
* Note:
*   - syntax looks as if typed on console (call qp2term)
*     <sh>pase utility</sh> runs "slower" because a child job
*     is created to run each PASE utility (normal Unix behavior).
*     All other XML/ILE functions run within XMLSERVICE job.
*   - Using nested shells within this sh shell may
*     produce unpredictable results.
*   - hex='on' before='' after='' -- same as <cmd> (1.7.0)
*       output (incompatible change hex/ccsid 1.7.4+):
*       <sh rows='on' hex='on' before='819/37' after='37/819'>
*         <row><hex>746F74616C2031363636313034</hex></row>
*       </sh>
*       output (rows='off' 1.7.4+):
*       <sh hex='on' before='819/37' after='37/819'>
*         <hex>746F74616C2031363636313034</hex>
*       </sh>
*   - error='on,off,fast' (1.7.6)
*       on  - script stops, full error report
*       off - script continues, job error log (default)
*       fast - script continues, brief error log
*************************************************************************

XMLSERVICE call QSH (1.9.8+)

Syntax can mostly be cut/paste from QSH shell (qsh).

*************************************************************************
* 2.5) call QSH utility (1.9.8+)
* XMLSERVICE allows calls of QSH utilities on IBM i. Typically, you cut/paste
* from a QSH command line. STRQSH is used for execution of your utilities.
* ---
* <qsh [rows='on|off'
*      hex='on'
*      before='cc1/cc2/cc3/cc4'
*      after='cc4/cc3/cc2/cc1'
*      error='on|off|fast'
*      ]>values (see QSH utility)</qsh>
* ---
* qsh         - shell tag
*  values     - (see QSH utility - qsh cut/paste)
* options
*  rows
*     on      - return rows lines
*     off     - return one string (default)
*  hex (1.7.4)
*     on      - input character hex (5147504C20202020)
*  before
*     cc(n)   - input ccsid1->ccsid2->ccsid3->ccsid4
*  after
*     cc(n)   - output ccsid1->ccsid2->ccsid3->ccsid4
* ---
*  error (1.7.6)
*     on      - script stops, full error report
*     off     - script continues, job error log (default)
*     fast    - script continues, brief error log
* ---
* example run QSH shell
*  <?xml version="1.0"?>
*  <xmlservice>
*  <qsh rows='on'>/usr/bin/system 'wrkactjob' | /usr/bin/grep -i fr</qsh>
*  </xmlservice>
* ---
* Note:
*   - Recommend qualify qsh utilities with /usr/bin.
*     This will avoid ccsid conversion between PASE/QSH utilities.
*   - syntax looks as if typed on console (qsh)
*     <qsh>QSH utility</qsh> runs "slower" because a child job
*     is created to run each QSH utility (normal Unix behavior).
*   - Using nested shells within this qsh shell may
*     produce unpredictable results.
*   - hex='on' before='' after='' -- same as <cmd> (1.7.0)
*   - error='on,off,fast' (1.7.6)
*       on  - script stops, full error report
*       off - script continues, job error log (default)
*       fast - script continues, brief error log

XMLSERVICE call PGM

Call PGM, SRVPGM, or system API, using XML syntax.

  1. Call PGM using this XML syntax.
  2. Call SRVPGM using this XML syntax.
*************************************************************************
* 3) call PGM/SRVPGM
* XMLSERVICE allows calls of *PGM and *SRVPGM on IBM i. Typically, you match
* call parameters, including data structures, and/or simple data elements.
* ---
* pgm name (*PGM or *SRVPGM)
* <pgm name=''
*      [lib=''
*       func=''
*       mode='opm|ile'
*       error='on|off|fast'
*       ]>values (see <parm> and <return>) </pgm>
* ---
* pgm         - IBM i *PGM or *SRVPGM name (tag)
*  values     - (see parm and return)
* options
*  lib
*     library - IBM i library name
*  func
*     function- IBM i *SRVPGM function name
*  mode
*     ile     - ILE and PASE memory (default)
*     opm     - ILE only memory (PASE can not view)
*  error (1.7.6)
*     on      - script stops, full error report
*     off     - script continues, job error log (default)
*     fast    - script continues, brief error log
*
* ---
* pgm parameters
* <parm [io='in|out|both|omit'
*        by='val|ref'
*        ]>values (see <ds> and <data>)</parm>
* ---
* parm        - parm name (tag)
*  values     - (see ds or data)
* options
*  io
*     in      - input only
*     out     - output only
*     both    - input/output only (default)
*     omit    - omit (1.2.3)
*  by
*     ref     - pass by reference (default)
*     val     - pass by value (1.9.9.3+)
*
* ---
* pgm return
* <return>values (see <ds> and <data>)</return>
* ---
* return      - return tag
*  values     - (see ds or data)
* options
*  na         - no options
*
* ---
* pgm data structure
* <ds [dim='n' dou='label'
*      len='label'
*      data='records'
*      ]>values (see <ds> or <data>)</ds>
* ---
* ds          - data structure tag
* values      - (see ds or data)
* options
*  dim
*   n         - array dimension value (default dim1)
*  dou
*   label     - match array dou terminate parm label (see data)
*  len (1.5.4)
*   label     - match calculate length of ds parm lable (see data)
*  data (1.7.5)
*   records   - data in records tag
*
* ---
* pgm data elements
* <data type='data types'
*       [dim='n'
*       varying='on|off|2|4'
*       enddo='label'
*       setlen='label'
*       offset='label'
*       hex='on|off' before='cc1/cc2/cc3/cc4' after='cc4/cc3/cc2/cc1'
*       trim='on|off'
*       next='label'
*       ]>(value)</data>
* ---
* data        - data value name (tag)
*  values     - value,
* type
*     3i0                   int8/byte     D myint8   3i 0
*     5i0                   int16/short   D myint16  5i 0
*     10i0                  int32/int     D myint32 10i 0
*     20i0                  int64/int64   D myint64 20i 0
*     3u0                   uint8/ubyte   D myint8   3u 0
*     5u0                   uint16/ushort D myint16  5u 0
*     10u0                  uint32/uint   D myint32 10u 0
*     20u0                  uint64/uint64 D myint64 20u 0
*     32a                   char          D mychar  32a
*     32a   {varying2} varchar            D mychar  32a   varying
*     32a   {varying4} varchar4           D mychar  32a   varying(4)
*     12p2                  packed        D mydec   12p 2
*     12s2                  zoned         D myzone  12s 2
*     4f2                   float         D myfloat  4f
*     8f4                   real/double   D myfloat  8f
*     3b                    binary        D mybin   (any)
*     40h                   hole (no out) D myhole  (any)
* options
*  dim
*     n       - array dimension value (default dim1)
*  varying
*     on      - character varying data (same as varying2)
*     off     - character non-varying data (default)
*     2       - character varying data
*     4       - character varying data
*  enddou
*     label   - match array dou terminate parm label (see ds)
*  setlen (1.5.4)
*     label   - match calculate length of ds parm lable (see ds)
*  offset
*     label   - match offset label (see overlay)
*  hex (1.6.8)
*     on      - input character hex (5147504C20202020)
*  before
*     cc(n)   - input ccsid1->ccsid2->ccsid3->ccsid4
*  after
*     cc(n)   - output ccsid1->ccsid2->ccsid3->ccsid4
*  trim (1.7.1)
*     on      - trim character (default)
*     off     - no trim character
*  next (1.9.2)
*     label   - match next offset label (see overlay)
*
* ---
* pgm parameters/return overlay
* <overlay
*       [io='in|out|both'
*        offset='n|label'
*        top='on|off|n'
*        setnext='nextoff'
*        ]>(see <ds> and <data>)</overlay>
* ---
* overlay     - structure overlay name (tag)
*  values     - (see ds or data)
* options
*  io
*     in      - input only
*     out     - output only
*     both    - input/output only (default)
*  offset
*     n       - overlay bytes offset relative
*     label   - overlay match bytes offset label (see data)
*  setnext (1.9.2)
*     label   - overlay match next offset label (see data)
*  top
*     n       - overlay parm number (see parm)
*     on      - overlay parm first (see parm)
*     off     - overlay parm last seen (see parm)
* ---
* example run a PGM
*  <?xml version="1.0"?>
*  <xmlservice>
*  <cmd>CHGLIBL LIBL(XMLSERVICE) CURLIB(XMLSERVICE)</cmd>
*  <pgm name='ZZCALL' lib=''>
*   <parm  io='both'>
*     <data type='1A' var='INCHARA'>a</data>
*   </parm>
*   <parm  io='both'>
*     <data type='1A' var='INCHARB'>b</data>
*   </parm>
*   <parm  io='both'>
*     <data type='7p4' var='INDEC1'>11.1111</data>
*   </parm>
*   <parm  io='both'>
*     <data type='12p2' var='INDEC2'>222.22</data>
*   </parm>
*   <parm  io='both'>
*    <ds>
*     <data type='1A' var='INDS1.DSCHARA'>x</data>
*     <data type='1A' var='INDS1.DSCHARB'>y</data>
*     <data type='7p4' var='INDS1.DSDEC1'>66.6666</data>
*     <data type='12p2' var='INDS1.DSDEC2'>77777.77</data>
*    </ds>
*   </parm>
*   <return>
*    <data type='10i0'>0</data>
*   </return>
*  </pgm>
*  </xmlservice>
* ---
* example run a SRVPGM
*  <?xml version="1.0"?>
*  <xmlservice>
*  <pgm name='ZZSRV' lib='XMLSERVICE' func='ZZARRAY'>
*   <parm comment='search this name'>
*    <data var='myName' type='10A'>Ranger</data>
*   </parm>
*   <parm comment='max allowed return'>
*    <data var='myMax' type='10i0'>5</data>
*   </parm>
*   <parm comment='actual count returned'>
*    <data var='myCount' type='10i0' enddo='mycount'>0</data>
*   </parm>
*   <return>
*    <ds var='dcRec_t' dim='999' dou='mycount'>
*      <data var='dcMyName' type='10A'>na</data>
*      <data var='dcMyJob' type='4096A'>na</data>
*      <data var='dcMyRank' type='10i0'>0</data>
*      <data var='dcMyPay' type='12p2'>0.0</data>
*    </ds>
*   </return>
*  </pgm>
*  </xmlservice>
* ---
* example optional ccsid convert name/lib format (1.6.8)
*  <?xml version="1.0"?>
*  <xmlservice>
*  <pgm>
*   <name hex='on' before='cc1/cc2/cc3/cc4'>bin2hex('&fredflin')</name>
*   <lib hex='on' before='cc1/cc2/cc3/cc4'>bin2hex('omlated')</lib>
*   <func hex='on' before='cc1/cc2/cc3/cc4'>bin2hex('me&proc')</func>
*   <parm>
*    <ds dim='3'>
*      <data type='1A'>a</data>
*    </ds>
*   </parm>
*   <return>
*    <ds dim='999'>
*      <data type='10i0'>0</data>
*    </ds>
*    </return>
*  </pgm>
*  </xmlservice>
* ---
* Note:
*   - data types (similar RPG):
*     ----------------------------------------------------------------------
*     int8/byte     D myint8   3i 0            <data type='3i0'/>
*     int16/short   D myint16  5i 0            <data type='5i0'/>
*     int32/int     D myint32 10i 0            <data type='10i0'/>
*     int64/int64   D myint64 20i 0            <data type='20i0'/>
*     uint8/ubyte   D myint8   3u 0            <data type='3u0'/>
*     uint16/ushort D myint16  5u 0            <data type='5u0'/>
*     uint32/uint   D myint32 10u 0            <data type='10u0'/>
*     uint64/uint64 D myint64 20u 0            <data type='20u0'/>
*     char          D mychar  32a              <data type='32a'/>
*     varchar       D mychar  32a   varying    <data type='32a' varying='2'/>
*     varchar4      D mychar  32a   varying(4) <data type='32a' varying='4'/>
*     packed        D mydec   12p 2            <data type='12p2'/>
*     zoned         D myzone  12s 2            <data type='12s2'/>
*     float         D myfloat  4f              <data type='4f2'/>
*     real/double   D myfloat  8f              <data type='8f4'/>
*     binary        D mybin   (any)            <data type='3b'>F0F1F2</data>
*     hole (no out) D myhole  (any)            <data type='40h'/>
*     ------------------------------------------------------------------------
*     type='na' [varying='on|off|2|4'] - character (32A)
*       <data type='32a'/>
*       <data type='32a' varying='on'>ranger</data>
*       <data type='32a'><![CDATA[<i am ranger>]]></data>
*       <data type='200A' hex='on' before='1208/930' after='930/1208'>
*       bin2hex($japan_raw_ascii_data)
*       </data>
*     type='npn' - packed decimal (12p2)
*       <data type='12p2'/>
*       <data type='12p2'>30.29</data>
*     type='nsn' - zoned decimal (12s2)
*       <data type='12s2'/>
*       <data type='12s2'>30.29</data>
*     type='nin' - signed integer (5i0, 10i0, 20i0)
*       <data type='20i0'/>
*       <data type='10i0'>-30</data>
*     type='nun' - unsigned integer (5u0, 10u0, 20u0)
*       <data type='20u0'/>
*       <data type='10u0'>30</data>
*     type='nfn' - floating point (4f2, 8f4)
*       <data type='4f2'/>
*       <data type='4f2'>30.34</data>
*       <data type='8f4'>30.34</data>
*     type='nb' - binary HEX char (2b, 400b)
*       <data type='5b'>F0F1F2CDEF</data>
*       <data type='2b'>1FBC</data>
*       <data type='2b'>0F0F</data>
*       - HEX upper case ('1FBC' not '1fbc')
*       - high/low bits (HEX='0F0F' not HEX='F0F')
*     type='nh' - 'hole' zero in, nothing out (4096h) (1.2.3)
*       <data type='400h'/>
*   - PGM/SRVPGM calls (<pgm>,<parm>,<data>,<return>) use syntax
*     that looks like RPG to describe the data parameters
*     (type='4b', type='32a', type='4f', type='10i0', type='12p2',
*     etc.).
*   - <data dim='n'> - dim='n' is new to 1.2 version and beyond,
*     older versions did not include this feature.
*   - Parameters using dou='label', enddo='label',
*     label must match for this to work,
*     then processing will only return records up to enddo limits.
*   - Type 'h' for 'hole' is used to input x'00' fill 'hole'
*     in the parameter geometry. It can be used to skip over
*     a chunk of complex data that you really did not want to
*     deal with or see in output XML. It is also very handy to
*     use with overlay when output data is variable
*     or unpredictable (1.2.3)
*     input:
*      <ds>
*        <data type='40a'>good stuff</data>     <---offset 0
*        <data type='400h'/>                    <---400 x'00' input
*        <data type='32a'>more good stuff</data><---offset 440
*      </ds>
*     output:
*      <ds>
*        <data type='40a'>stuff back</data>     <--- offset 0
*        <data type='400h'> </data>             <--- ignored output
*        <data type='32a'>stuff back</data>     <--- offset 440
*      </ds>
*   - Added parm='omit' for RPG OPTIONS(*OMIT) parameter. A
*     *NULL will be passed in this location.
*     All parm io='omit' will be excluded from XML
*     output returned because *NULL parameter has no data (1.2.3).
*       <parm comment='my name' io='omit'>
*         <data var='myName' type='10A'>Ranger</data> <--ignore *NULL
*       </parm>
*       RPG procedure (SRVPGM function):
*       D zzomit PI 50A varying
*       D myName 10A options(*OMIT) <---- optional omitted (*NULL)
*       D yourName 10A
*   - Added len='label'/setlen='label' to allow for
*     automatic length calculation for various system
*     APIs that want a %size(thing) parameter.
*     This should work across parameters and within
*     parameters (any order), but nesting len/setlen is
*     not allowed.
*       <parm  io="both" comment='Error code'>
*        <ds comment='Format ERRC0100' len='rec2'>
*         <data type='10i0' comment='returned'>0</data>
*         <data type='10i0' comment='available' setlen='rec2'>0</data>
*         <data type='7A' comment='Exception ID'> </data>
*         <data type='1A' comment='Reserved'> </data>
*        </ds>
*       </parm>
*   - Up to four conversions can take place
*     for the truly complex ccsid issues (1.6.8)
*      <data type='A' hex='on' before='cc1/cc2/cc3/cc4' after='cc4/cc3/cc2/cc1'>
*      flow:
*      -> PHP client bin2hex('wild_ascii_raw_chars')
*      -> xmlservice hex2bin back to 'wild_ascii_raw_chars'
*      -> xmlservice convert cc1->cc2->cc3->cc4 (before)
*      -> xmlservice make ILE call
*      -> xmlservice convert cc4->cc3->cc2->cc1 (after)
*      -> xmlservice tohex "xml_hex_back"
*      -> PHP client $chars = pack('H*',"xml_hex_back")
*   - V5R4 accomidation for OPM programs like CLP (1.6.8)
*      - mode='opm' uses non-teraspace memory to build parm lists
*        that are used with _CALLPGMV for a "pure" OPM call mode
*      - mode='ile' default using teraspace for "mixed" memory
*        compatible with PASE calls (IBM i possiblilities)
*   - Allow trim control character/binary <data ... trim='on|off'>
*      - trim='on'  -- right trim  (default character type='na')
*      - trim='off' -- include all (default binary type='nb')
*   - see <overlay> for offset='label'
*      <data offset='label'>            <-- memory location to pop off a
*                                           variable/changing offset value
*                                           for use in overlay()
*      <overlay top='n' offset='label'> <-- top='n' overlay parameter 'n',
*                                           then add offset='label' pop value
*      - offset='label' allows label location to pop off a <data> offset value
*        at this data location to add position offset <overlay offset='label'>
*      - 'label' is NOT a position location for <overlay>, it only holds
*        a offset value in this <data> memory location for things like
*        system APIs with offset-2-next.
*   - data='records' - data follows in record format
*      fast "many records" i/o big data (see below)  (1.7.5)
*      <parm comment='wsopstdcnt'>
*       <data type='3s0' enddo='wsopstdcnt'/>
*      </parm>
*      <parm comment='findMe1'>
*       <ds var='findMe1' data='records'>
*        <ds var='dcRec1_t' array='on'>
*         <ds var='dcRec1_t'>
*          <data var='dcMyName1' type='10A'/>
*          <ds var='dcRec2_t'>
*           <data var='dcMyName2' type='10A'/>
*           <ds var='dcRec3_t'>
*            <data var='dcMyName3' type='10A'/>
*            <ds var='dcRec_t' dim='999' dou='wsopstdcnt'>
*             <data var='dcMyName' type='10A'/>
*             <data var='dcMyJob' type='4096A'/>
*             <data var='dcMyRank' type='10i0'/>
*             <data var='dcMyPay' type='12p2'/>
*            </ds>
*           </ds>
*          </ds>
*         </ds>
*        </ds>
*       </ds>
*       <records delimit=':'>:Rgr:B:Ok:nd1:nd1:1:1.1:...:</records>
*      </parm>
*      a) <records delimit=':'> simply match in order input
*         of any complex structure. Output matches
*         order input (see above)
*      b) <records  delimit=':'> delimit can be any character
*         not in your complex records (see above)
*      c) works with any <parm> or <return>
*      d) dou/enddo works, but tricky script to design (be careful)
*   - setnext='nextoff' / next='nextoff' - see overlay  (1.9.2)
*   - len/setlen - auto-len calculate ds setlen='here' (1.5.4)
*   - error='on,off,fast' (1.7.6)
*       on  - script stops, full error report (default)
*       off - script continues, job error log
*      fast - script continues, brief error log
*   - pgm parameters/return overlay (custom offset='bytes', input/output):
*     <overlay> works "relative" to "previous" <parm> in
*               "order of appearance XML"
*               or absolute position to (top='n')
*     <pgm>
*     --->absolute parm             <---relative parm
*     ---><parm>complex stuff</parm><-------------------
*     |   <overlay>complex over parm 1   </overlay>____|
*     |
*     |--><parm>complex stuff</parm><-------------------
*     ||  <overlay>complex over parm 2   </overlay>____|
*     ||  :
*     ||  <parm>complex stuff</parm><-------------------
*     ||  <overlay>complex over last parm</overlay>____|
*     ||  :
*     |___<overlay top='on'>over top parm</overlay>
*      |  :
*      |__<overlay top='2'>over parm 2  </overlay>
*     </pgm>
*   - top='on|n' allow overlay position to parameter n
*      ... top='on' absolute parm='1' (1.2.1)
*      ... top='n' absolute parm='n' (1.2.2)
*      ... offset='n' bytes offset relative
*          to top='n' position (parm 1,2,3, etc)
*   - Once the top='n' parm location is etablished, offset='n'
*     will move overlay to offset within the parameter.
*     <data offset='label'>            <-- memory location to pop off a
*                                          variable/changing offset value
*                                          for use in overlay()
*     <overlay top='n' offset='label'> <-- top='n' overlay parameter 'n',
*                                          then add offset='label' pop value
*     - offset='label' allows label location to pop off a <data> offset value
*       at this data location to add position offset <overlay offset='label'>
*     - 'label' is NOT a position location for <overlay>, it only holds
*       a offset value in this <data> memory location for things like
*       system APIs with offset-2-next.
*   - setnext='nextoff' / next='nextoff'  (1.9.2)
*      <pgm name='QSZRTVPR'>
*       <parm io='both'>
*        <ds comment='PRDR0200'>
*         :
*         <data type='10i0' offset='myOffset'></data>
*         :
*       </ds>
*      </parm>
*       :
*      <overlay io='out' top='1' offset='myOffset'>
*       <ds>
*        <data type='10A'></data>
*        <data type='2A'></data>
*        <data type='10i0' enddo='prim'></data>
*        <data type='10i0' offset='myOffset2'></data>
*       </ds>
*       </overlay>
*       <overlay io='out' top='1' offset='myOffset2'
*                dim='10' dou='prim' setnext='nextoff'>
*       <ds>
*        <data type='10i0' next='nextoff'></data>
*        <data type='10A'></data>
*        <data type='10A'></data>
*        <data type='10A'></data>
*        <data type='10A'></data>
*        <data type='10A'></data>
*        <data type='10A'></data>
*        <data type='10i0'></data>
*        <data type='10A'></data>
*       </ds>
*      </overlay>
*************************************************************************

Note: Additional XML attributes added for comments, var names, and documentation will simply be returned untouched, so you may build your own label conventions for XML parsing in client code.

Hint: use your own labels and comments XML attributes for easy client XML parsing work (var=’MYVAR’).

Advanced CCSID

Using default PHP toolkit DB2 clob interface (iPLUGxxx/iPLUGRxxx), ccsid conversion occurs naturally as DB2 client/server and you will not have to code before/after, but method is available if you have a specific concern or you have scripts returning many different languages.

Theory follows that most of XML document “intent” will remain immutable across db2 clob conversion (keywords, numbers, etc.), but for character data on some occasion there will be mixed/competing client/server ccsid conversion intention (client running 819, but data 1208), or there may be multiple language ccsid in the same XML document request (German, English, French), therefore using a combination of transfer in hex (hex=‘on’) and IBM i server ccsid translation before/after should allow complete control over transforms at user need.

Example:

<data type='200A' hex='on' before='819/424' after='424/819'>bin2hex('Hebrew_ascii_raw_chars')</data>
<data type='200A' hex='on' before='819/1098' after='1098/819'>bin2hex('Farsi_ascii_raw_chars')</data>
<data type='200A' hex='on' before='819/880' after='880/819'>bin2hex('Russia_ascii_raw_chars')</data>
<data type='200A' hex='on' before='819/280' after='280/819'>bin2hex('Italy_ascii_raw_chars')</data>
<data type='200A' hex='on' before='819/273' after='273/819'>bin2hex('Germany_ascii_raw_chars')</data>
<data type='200A' hex='on' before='819/1088' after='1088/819'>bin2hex('Korea_ascii_raw_chars')</data>
<data type='200A' hex='on' before='1208/13488' after='13488/1208'>bin2hex('Japan_ascii_raw_chars')</data>
<data type='200A' hex='on' before='1208/13488' after='13488/1208'>bin2hex('China_ascii_raw_chars')</data>
where:
before    - XMLSERVICE convert CCSID before ILE program call
after     - XMLSERVICE convert CCSID after ILE program call for client return
bin2hex() - script hex string unaltered ascii image (also returned hex string avoid any conversion)
pack()    - script uses pack('H*',"xml_hex_back") function in PHP program for ascii characters
Note:
Up to four conversions can take place for the truly complex ccsid conversion issues
<data type='A' hex='on' before='cc1/cc2/cc3/cc4' after='cc4/cc3/cc2/cc1'>bin2hex('wild_ascii_raw_chars')</data>
flow:
-> PHP client bin2hex('wild_ascii_raw_chars')
-> xmlservice hex2bin back to 'wild_ascii_raw_chars'
-> xmlservice convert cc1->cc2->cc3->cc4 (before)
-> xmlservice make ILE call
-> xmlservice convert cc4->cc3->cc2->cc1 (after)
-> xmlservice tohex "xml_hex_back"
-> PHP client $chars = pack('H*',"xml_hex_back")

XMLSERVICE DB2 SQL

DB2 queries using only XML with syntax cut/paste STRSQL (cool, cool, cool version 1.5+).

Note: DB2 SQL XML does not work in-line stateless ($ctl=’*here’), but works fine with normal private connections (ipc=’/tmp/fred’, $ctl=’*sbmjob’).

*****************************************************
* Run XML SQL statements:
*  <sql>...</sql> - start/end run XML SQL statements
*
* Example easy way (chglibl, default connection, default statement):
*  Input:
*   <?xml version='1.0'?>
*   <script>
*   <cmd>CHGLIBL LIBL(XMLSERVTST QTEMP) CURLIB(XMLSERVTST)</cmd>
*   <sql>
*   <query>select breed, name from animal</query>
*   <fetch block='all' desc='on'/>
*   </sql>
*   </script>
*   Note: You only need chglibl once for all scripts because
*         XMLSERVICE jobs remain active until killed (*immed).
*         XMLSERVICE default is system naming (not sql naming),
*         so library list is significant for unqualified
*         statements (see options below).
*  Output:
*   <?xml version='1.0'?>
*   <script>
*   <cmd>+++ success CHGLIBL LIBL(XMLSERVTST QTEMP) CURLIB(XMLSERVTST)</cmd>
*   <sql>
*   <query conn='conn1' stmt='stmt1'>
*   +++ success select breed, name from animal</query>
*   <fetch block='all' desc='on' stmt='stmt1'>
*   <row><data desc='BREED'>cat</data><data desc='NAME'>Pook</data></row>
*   <row><data desc='BREED'>dog</data><data desc='NAME'>Peaches</data></row>
*   <row><data desc='BREED'>horse</data><data desc='NAME'>Smarty</data></row>
*   <row><data desc='BREED'>gold fish</data><data desc='NAME'>Bubbles</data></row>
*   <row><data desc='BREED'>budgerigar</data><data desc='NAME'>Gizmo</data></row>
*   <row><data desc='BREED'>goat</data><data desc='NAME'>Rickety Ride</data></row>
*   <row><data desc='BREED'>llama</data><data desc='NAME'>Sweater</data></row>
*   </fetch>
*   </sql>
*   </script>
*
* Reserved SQL words (syntax):
* <sql>
*
* Connect to DB2 (optional):
* <connect [conn='label' db='x' uid='x' pwd='x' options='label']/>
*
* Options template (optional):
* <options [options='label' error='on|off|fast'
*  Environment level (SQLSetEnvAttr) ...
*   servermode='on'                               (default=off)
*  Connection level (SQLSetConnAttr) ...
*   autocommit='on|off'                           (default=on)
*   commit='none|uncommitted|committed|repeatable|serializable'
*                                         (default=uncommitted)
*   naming='system|sql'                           (default=system)
*   sqllib='mylib'                                (default=na)
*   libl='curlib1 mylib2 mylib3'                  (default=na)
*   datefmt='iso|usa|eur|jis|mdy|dmy|ymd|jul|job' (default=na)
*   datesep='slash|dash|period|comma|blank|job'   (default=na)
*   timefmt='iso|usa|eur|jis|hms|job'             (default=na)
*   timesep='colon|period|comma|blank|job'        (default=na)
*   decimalsep='period|comma|blank|job'           (default=na)
*   optimize='first|all'                          (default=na)
*  Statement level (SQLSetStmtAttr) (version 1.5.1+) ...
*   scrollable='on|off'                           (default=off)
*   sensitive='unspecified|sensitive|insensitive' (default=unspecified)
*   cursor='forward|static|dynamic'               (default=forward)
*   fetchonly='on|off'                            (deafult=on)
*   fullopen='on|off'                             (deafult=off)
* ]/>
* These alternate db2 features available ...
*       error='on,off,fast' (1.7.6)
*              on  - script stops, full error report (default)
*              off - script continues, job error log
*              fast - script continues, brief error log
* Note:
*  commit sets transaction-isolation level
*  - none         - no commit        (*NONE)
*  - uncommitted  - uncommitted read (*CHG)
*  - committed    - cursor stability (*CS)
*  - repeatable   - read stability   (*RS, *ALL)
*  - serializable - repeatable read  (*RR, *ALL)
*
* Commit or rollback transaction (optional):
* <commit [conn='label' action='rollback' error='on|off|fast']/>
* These alternate db2 features available ...
*       error='on,off,fast' (1.7.6)
*              on  - script stops, full error report (default)
*              off - script continues, job error log
*              fast - script continues, brief error log
*
* Execute statement directly (SQLExecDirect):
* <query [conn='label' stmt='label' options='label' error='on|off|fast']>
*  call storedproc('fred flinrock',42.42)
*    -- or --
*  select * from table where abc = 'abc'
*    -- or (use CDATA if xml trouble special characters) --
*  <![CDATA[select * from animal where ID < 5 and weight > 10.0]]>
* </query>
* These alternate db2 features available ...
*       error='on,off,fast' (1.7.6)
*              on  - script stops, full error report (default)
*              off - script continues, job error log
*              fast - script continues, brief error log
* Note:
* - options='label' (version 1.5.1+)
*
* Prepare a statement (SQLPrepare/SQLExecute):
* <prepare [conn='label' stmt='label' options='label' error='on|off|fast']>
*  call storedproc(?,?)
*    -- or --
*  select * from table where abc = ?
*    -- or (use CDATA if xml trouble special characters) --
*  <![CDATA[select * from animal where ID < ? and weight > ?]]>
* </prepare>
* These alternate db2 features available ...
*       error='on,off,fast' (1.7.6)
*              on  - script stops, full error report (default)
*              off - script continues, job error log
*              fast - script continues, brief error log
* Note:
* - options='label' (version 1.5.1+)
*
* Execute a prepared statement (SQLPrepare/SQLExecute):
* <execute [stmt='label'  error='on|off|fast']>
* These alternate db2 features available ...
*       error='on,off,fast' (1.7.6)
*              on  - script stops, full error report (default)
*              off - script continues, job error log
*              fast - script continues, brief error log
*   <parm [io='in|out|both']>my string</parm>
*   <parm [io='in|out|both']>42.42</parm>
* </execute>
* Note:
* - substitution parameters '?' are taken in order of
*   appearence with data types decided internally in
*   XMLSERVICE (SQLNumParams/SQLDescribeParam)
*   and bound at call time (SQLBindParameter)
*
* Fetch result(s) of a statement:
* <fetch [stmt='label' block='all|n' rec='n' desc='on|off' error="on|off|fast"/>
*                      (default=all)         (default=on)  (default='off')
* These alternate db2 features available ...
*       error='on,off,fast' (1.7.6)
*              on  - script stops, full error report
*              off - script continues, job error log (default)
*              fast - script continues, brief error log
*   Output (column description included via desc='on'):
*   <fetch>
*    <row><data desc='NAME'>Rip</data><data desc='ID'>9</data></row>
*    <row><data desc='NAME'>Bee</data><data desc='ID'>3</data></row>
*   </fetch>
* Note:
* - result set column types and descriptions are
*   decided internally in XMLSERVICE (SQLNumResultCols/SQLDescribeCol)
*   and bound at call time (SQLBindCol)
* - rec='n' (version 1.5.1+)
*
* After query/execute statement:
* Statment rows affected by change (SQLRowCount):
* <rowcount [stmt='label' error='on|off']/>
*                         (default=off)
*  Output:
*   <rowcount ...>24</rowcount>
*
* After insert of identity id:
* Statment last identity id (IDENTITY_VAL_LOCAL):
* <identity [conn='label' error='on|off']/>
*                         (default=off)
*  Output:
*   <identity ...>23</identity>
*
* Statment describe parms (SQLNumParams/SQLDescribeParam):
*                   columns (SQLNumResultCols/SQLDescribeCol):
* <describe [stmt='label' desc='col|parm|both' error='on|off']/>
*                                              (default=off)
*  Output (parm):
*   <describe ...>
*    <parm> or <col>
*      <name>FRED</name>
*      <dbtype>DECIMAL</dbtype>
*      <size>12</size>
*      <scale>2</scale>
*      <nullable>0</nullable>
*    </parm> or </col>
*   </describe>
*
* Statment count parms (SQLNumParams):            (1.7.6)
*                columns (SQLNumResultCols):
* <count [stmt='label' desc='col|parm|both' error='on|off']/>
*                                           (default=off)
*  Output (parm):
*   <count ...>
*    <colcount>nbr</colcount>
*    <parmcount>nbr</parmcount>
*   </count>
*
* Free resources:
* <free [conn='all|label'
*        cstmt='label'
*        stmt='all|label'
*        options='all|label'
*        error='on|off|fast']/>
* These alternate db2 features available ...
*       error='on,off,fast' (1.7.6)
*              on  - script stops, full error report
*              off - script continues, job error log (default)
*              fast - script continues, brief error log
*
* Meta data - tables:
* <tables [conn='label' error='on|off|fast'>
*                       (default=off)
*   <parm>qualifier or catalog<parm>
*   <parm>schema name</parm>
*   <parm>table name</parm>
*   <parm>table type</parm>
* </tables>
* Output (desc varies V5R4 to V6+):
*   <tables ...>
*     <row>
*       <data desc='TABLE_CAT'>LP0164D</data>
*       <data desc='TABLE_SCHEM'>XMLSERVTST</data>
*       <data desc='TABLE_NAME'>ANIMAL</data>
*       <data desc='TABLE_TYPE'>BASE TABLE</data>
*       <data desc='REMARKS'></data>
*     </row>
*   </tables>
*
* Meta data - table privileges:
* <tablepriv [conn='label' error='on|off|fast'>
*                       (default=off)
*   <parm>qualifier or catalog<parm>
*   <parm>schema name</parm>
*   <parm>table name</parm>
* </tablepriv>
* Output (desc varies V5R4 to V6+):
*   <tablepriv ...>
*     <row>
*       <data desc='TABLE_CAT'>LP0164D</data>
*       <data desc='TABLE_SCHEM'>XMLSERVTST</data>
*       <data desc='TABLE_NAME'>ANIMAL</data>
*       <data desc='GRANTOR'></data>
*       <data desc='GRANTEE'>PUBLIC</data>
*       <data desc='PRIVILEGE'>SELECT</data>
*       <data desc='IS_GRANTABLE'>NO</data>
*       <data desc='DBNAME'></data>
*     </row>
*   </tablepriv>
*
* Meta data - columns:
* <columns [conn='label' error='on|off|fast'>
*                        (default=off)
*   <parm>qualifier or catalog</parm>
*   <parm>schema name</parm>
*   <parm>table name</parm>
*   <parm>column name</parm>
* </columns>
* Output (desc varies V5R4 to V6+):
*   <columns ...>
*     <row>
*       <data desc='TABLE_CAT'>LP0164D</data>
*       <data desc='TABLE_SCHEM'>XMLSERVTST</data>
*       <data desc='TABLE_NAME'>ANIMAL</data>
*       <data desc='COLUMN_NAME'>BREED</data>
*       <data desc='DATA_TYPE'>12</data>
*       <data desc='TYPE_NAME'>VARCHAR</data>
*       <data desc='LENGTH_PRECISION'>0</data>
*       <data desc='BUFFER_LENGTH'>34</data>
*       <data desc='NUM_SCALE'>0</data>
*       <data desc='NUM_PREC_RADIX'>0</data>
*       <data desc='NULLABLE'>1</data>
*       <data desc='REMARKS'></data>
*       <data desc='COLUMN_DEF'>
*       </data><data desc='DATETIME_CODE'>0</data>
*       <data desc='CHAR_OCTET_LENGTH'>32</data>
*       <data desc='ORDINAL_POSITION'>2</data>
*     </row>
*   </columns>
*
* Meta data - special columns:
* <special [conn='label' error='on|off|fast'>
*                           (default=off)
*   <parm>qualifier or catalog</parm>
*   <parm>schema name</parm>
*   <parm>table name</parm>
*   <parm>row|transaction|session</parm>
*   <parm>no|nullable</parm>
* </special>
*
* Meta data - column privileges:
* <columnpriv [conn='label' error='on|off|fast'>
*                           (default=off)
*   <parm>qualifier or catalog</parm>
*   <parm>schema name</parm>
*   <parm>table name</parm>
*   <parm>column name</parm>
* </columnpriv>
* Output (desc varies V5R4 to V6+):
*   <columnpriv ...>
*     <row>
*       <data desc='TABLE_CAT'>LP0164D</data>
*       <data desc='TABLE_SCHEM'>XMLSERVTST</data>
*       data desc='TABLE_NAME'>ANIMAL</data>
*       <data desc='COLUMN_NAME'>BREED</data>
*       <data desc='GRANTOR'></data>
*       <data desc='GRANTEE'>PUBLIC</data>
*       <data desc='PRIVILEGE'>SELECT</data>
*       <data desc='IS_GRANTABLE'>NO</data>
*       <data desc='DBNAME'></data>
*     </row>
*   </columnpriv>
*
* Meta data - procedures:
* <procedures [conn='label' error='on|off|fast'>
*                        (default=off)
*   <parm>qualifier or catalog</parm>
*   <parm>schema name</parm>
*   <parm>procedure name</parm>
* </procedures>
* Output (desc varies V5R4 to V6+):
*   <procedures ...>
*     <row>
*       <data desc='PROCEDURE_CAT'>LP0164D</data>
*       <data desc='PROCEDURE_SCHEM'>XMLSERVTST</data>
*       <data desc='PROCEDURE_NAME'>MATCH1</data>
*       <data desc='NUM_INPUT_PARAMS'>2</data>
*       <data desc='NUM_OUTPUT_PARAMS'>1</data>
*       <data desc='NUM_RESULT_SETS'>1</data>
*       <data desc='REMARKS'></data>
*     </row>
*
* Meta data - procedure columns:
* <pcolumns [conn='label' error='on|off|fast'>
*                        (default=off)
*   <parm>qualifier or catalog</parm>
*   <parm>schema name</parm>
*   <parm>proc name</parm>
*   <parm>column name</parm>
* </pcolumns>
* Output (desc varies V5R4 to V6+):
*   <pcolumns ...>
*     <row>
*       <data desc='PROCEDURE_CAT'>LP0164D</data>
*       <data desc='PROCEDURE_SCHEM'>XMLSERVTST</data>
*       <data desc='PROCEDURE_NAME'>MATCH1</data>
*       <data desc='COLUMN_NAME'>FIRST_NAME</data>
*       <data desc='COLUMN_TYPE'>1</data>
*       <data desc='DATA_TYPE'>12</data>
*       <data desc='TYPE_NAME'>CHARACTER VARYING</data>
*       <data desc='PRECISION'>0</data>
*       <data desc='LENGTH'>128</data>
*       <data desc='SCALE'>0</data>
*       <data desc='RADIX'>0</data>
*       <data desc='NULLABLE'>YES</data>
*       <data desc='REMARKS'></data>
*       <data desc='COLUMN_DEF'>0</data>
*       <data desc='SQL_DATA_TYPE'>12</data>
*       <data desc='SQL_DATETIME_SUB'>0</data>
*       <data desc='CHAR_OCTET_LENGTH'>128</data>
*       <data desc='ORDINAL_POSITION'>1</data>
*       <data desc='IS_NULLABLE'>YES</data>
*     </row>
*   </pcolumns>
*
* Meta data - primary keys:
* <primarykeys [conn='label' error='on|off|fast'>
*                           (default=off)
*   <parm>qualifier or catalog</parm>
*   <parm>schema name</parm>
*   <parm>table name</parm>
* </primarykeys>
* Output (desc varies V5R4 to V6+):
*   <primarykeys ...>
*     <row>
*       <data desc='TABLE_CAT'>LP0164D</data>
*       <data desc='TABLE_SCHEM'>XMLSERVTST</data>
*       <data desc='TABLE_NAME'>ANIMAL2</data>
*       <data desc='COLUMN_NAME'>NOTEID</data>
*       <data desc='KEY_SEQ'>1</data>
*       <data desc='PK_NAME'>Q_XMLSERVTST_ANIMAL2_NOTEID_00001</data>
*     </row>
*   </primarykeys>
*
* Meta data - foreign keys:
* <foreignkeys [conn='label' error='on|off|fast'>
*                           (default=off)
*   <parm>primary qualifier or catalog</parm>
*   <parm>primary schema name</parm>
*   <parm>primary table name</parm>
*   <parm>foreign qualifier or catalog</parm>
*   <parm>foreign schema name</parm>
*   <parm>foreign table name</parm>
* </foreignkeys>
* Output (desc varies V5R4 to V6+):
*   <foreignkeys ...>
*     <row>
*       <data desc='PKTABLE_CAT'>LP0164D</data>
*       <data desc='PKTABLE_SCHEM'>XMLSERVTST</data>
*       <data desc='PKTABLE_NAME'>ANIMAL2</data>
*       <data desc='PKCOLUMN_NAME'>NOTEID</data>
*       <data desc='FKTABLE_CAT'>LP0164D</data>
*       <data desc='FKTABLE_SCHEM'>XMLSERVTST</data>
*       <data desc='FKTABLE_NAME'>FKEY2</data>
*       <data desc='FKCOLUMN_NAME'>IDF</data>
*       <data desc='KEY_SEQ'>1</data>
*       <data desc='UPDATE_RULE'>3</data>
*       <data desc='DELETE_RULE'>3</data>
*       <data desc='FK_NAME'>Q_XMLSERVTST_FKEY2_IDF_00001</data>
*       <data desc='PK_NAME'>Q_XMLSERVTST_ANIMAL2_NOTEID_00001</data>
*     </row>
*   </foreignkeys>
*
* Meta data - statistics:
* <statistics [conn='label' error='on|off|fast'>
*                           (default=off)
*   <parm>qualifier or catalog</parm>
*   <parm>schema name</parm>
*   <parm>table name</parm>
*   <parm>all|unique</parm>
* </statistics>
* Output (desc varies V5R4 to V6+):
*   <statistics ...>
*     <row>
*       <data desc='TABLE_CAT'></data>
*       <data desc='TABLE_SCHEM'>XMLSERVTST</data>
*       <data desc='TABLE_NAME'>ANIMAL2</data>
*       <data desc='NON_UNIQUE'>1</data>
*       <data desc='00005'>XMLSERVTST</data>
*       <data desc='00006'>INDEX2</data>
*       <data desc='TYPE'>3</data>
*       <data desc='ORDINAL_POSITION'>1</data>
*       <data desc='00009'>ANIMALID</data>
*       <data desc='COLLATION'>A</data>
*       <data desc='CARDINALITY'>0</data>
*       <data desc='PAGES'>0</data>
*     </row>
*   </statistics>
*
* </sql>
*
* NOTES:
*
* The XMLSERVICE sql rules:
* > Connection rules:
*   - if connect is omitted, then XMLSERVICE will open
*     a default connection under the current profile.
*   - if servermode='na' (off default), ONLY ONE connection
*     allowed for the XMLSERVICE job. This is a DB2 rule of
*     one activation equals one active connection/transaction,
*     so you must free/commit a connection/transaction before
*     attempting to create a new connection/transaction
*     (or connect another profile). This is the correct
*     mode for sharing QTEMP with XMLSERVICE called
*     PGMs/SRVPGMs, so it is also the XMLSERVICE default.
*   - if servermode='on', XMLSERVICE may have multiple
*     connection/transactions active, BUT each connection
*     will be running in a seperate QSQSRVR job. This means
*     QTEMP in XMLSERVICE job will NOT BE USEABLE to communicate
*     between XMLSERVICE called PGM/SRVPGM, etc. (QTEMP useless).
*     Once an XMLSERVICE job enters servermode='on',
*     it will NEVER stop using server mode until the
*     process is ended (choose wisely).
* > Commit rules (see options):
*   - default is autocommit='on', where all create, insert,
*     delete actions will be committed at operation time.
*   - if autocommit='off', commit action may be delayed
*     across multiple requests to XMLSERVICE and you
*     may also rollback the transaction.
* > Statement rules:
*   - if stmt='label' is omitted (normal), the first active
*     statement available will be used for a sql operation
*     such as fetch or describe. Therefore it is not wise
*     to mix stmt='label' and omitted in the same XMLSERVICE
*     task.
*   - if a stmt is left active (no free), and a subsequent
*     'label'/omitted is attempted, the active statement will
*     be released along with all result sets, etc. (free),
*     and the new statement will become the active statement.
*     Therefore if you are attempting to use multiple result
*     sets from different queries, you should manually
*     specify stmt='label' to avoid fetching from the
*     wrong result set.
* > Options rules:
*   -Using servermode='on' universally executes statements
*    in child process (QSQSRVR jobs), not XMLSERVICE job.
*    Once servermode='on', it stays 'on' for all connections
*    for the life of the XMLSERVICE job, therefore you cannot
*    expect to share QTEMP between PGM calls/DB2 SQL, etc.
*    (ie. think very careful before you use this option)
*   -Default mode is servermode='na' (off), or 'normal mode',
*    which means a single connect/transaction is allowed
*    active at any given time. Therefore, you must end
*    any transation with <commit> and <free conn='all'>,
*    before attempting to switch between connection profiles.
*    (normal CLI rules transaction/connect in a single process)
*   -System vs. SQL naming libl, where:
*    - naming='system' with libl='curlib1 mylib2' (list)
*      example: select * from mylib3/table (specific library)
*               select * from table (uses library list)
*      (system naming is the default naming mode XMLSERVICE)
*    - naming='sql' with *sqllib='mylib' (one)
*      example: select * from mylib3.table (specific schema)
*               select * from table (uses *sqllib='schema')
*    Do not try to mix these two modes in the same
*    connection as this always leads to program errors
*    (ie. make up your mind before you write your scripts).
* > Connection/statements/options label rules (optional labels):
*   conn='label'    - unique name connection (optional)
*   stmt='label'    - unique name statement (optional)
*   options='label' - unique name options template (optional)
*   - a label is ten characters or less
*   - a unique 'label' is used as XMLSERVICE/DB2 routing 'key'
*     thereby allowing multiple XML <sql> calls to XMLSERVICE
*     routing back to open/active DB2 connection(s)/statement(s)
*   - If optional conn/stmt 'label' is omitted, XMLSERVICE
*     will return a unique 'label' attribute in output XML
*     for subsequent sql prepare, execute, fetch, etc.
*   - If optional conn/stmt 'label' is omitted, XMLSERVICE
*     will attempt to use any active conn/stmt. No 'label(s)'
*     works just fine with less XML, but you need to be very careful
*     that other scripts do not introduce additional conn/stmt
*     'label(s)' that spoil your generic XML DB2 statements.
* > Connection/statements/options free rules (optional free):
*   - Connections/statements remain active/open until released:
*     <free/>                 - release all
*     <free options='label'/> - release options template
*     <free options='all'/>   - release all options template
*     <free conn='label'/>    - release connection (and statements)
*     <free conn='all'/>      - release all connections (and statements)
*     <free cstmt='label'/>   - release all statements 'label' connection
*     <free stmt='label'/>    - release this statement
*     <free stmt='all'/>      - release all statements
*   - conn='all'   : free all connections,
*                    also frees all statements
*   - cstmt='label': free all statements under 'label' connection,
*                    other connections/statements remain active
*   - stmt='all'   : free all statements,
*                    connections remain active
* > These alternate db2 features available ...
*       error='on,off,fast' (1.7.6)
*              on  - script stops, full error report
*              off - script continues, job error log
*              fast - script continues, brief error log
*
*****************************************************

XMLSERVICE job log

Job log info has been added to all XMLSERVICE returned fatal errors. An additonal function was also added to retrieve a job of the XMLSERVICE job or another job (1.5.8+).

* 0) Optional get diagnostics (1.5.8)
* <diag [info='joblog|conf' job='job' user='uid' nbr='nbr']/>
*   example run
*     <?xml version="1.0"?>
*     <diag info='joblog'/>
*     Note:
*       The current XMLSERVICE job log is assumed if optional
*       attributes job='job' user='uid' nbr='nbr' missing.
*       if you wish to provide custom diagnostics,
*       info='conf' calls optional hook in plugconfx.

One sample by value (xmlservice 1.9.9.3+)

This is only about RPG parameters marked ‘const’ or ‘value’. If you do not understand these ‘by value’ RPG parameter types, this will only confuse (ignore).

A fix was added for by value in xmlservice 1.9.9.3. All data types test work except zoned decimal by value (although may work). This will be handled later in next base version of xjservice. Please note by="val" is an attribute of <parm>, not <data> (see below).

BTW – Attribute of <parm by='val'> is not an error (no debate). That is, by='ref,val' is indeed attribute of <parm>, same as io='in,out,both', and other exotic parameter attributes like *omit and *nopass. Unfortunately RPG syntax obfuscates spoken words ‘passing parameters’ into minds eye of ‘passing data(s) and structures and arrays of data’ (which make no sense). Aka, <parm by='val' io='in'> is correct.

dcl-proc GetPacked export;
dcl-pi  *N;
  i2d char(8) Value;
  p1 packed(4:2) Value;
  p2 packed(3:2) Value;
  p3 packed(12:2) Value;
  p4 packed(6:2) Value;
  p5 packed(8:2) Value;
  p6 packed(24:4) Value;
  p7 packed(48:8) Value;
  ppd char(15) Value;
  zzd char(30);
  i2 int(5) value;
  i1d char(30);
  i4 int(10) value;
  i8 int(20) value;
  f4 float(4) value;
  f4d char(30);
  f8 float(8) value;
  i4d char(30);
  i8d char(30);
  f8d char(30);
  i1 int(3) value;
end-pi;
  p1 += 2.22;
  p2 += 2.22;
  p3 += 2.22;
  p4 += 2.22;
  p5 += 2.22;
  p6 += 2.22;
  p7 += 2.22;
  ppd = 'pack man';
  zzd = 'zone man';
  i1 += 2;
  i1d = 'byte man';
  i2 += 2;
  i2d = 'short man';
  i4 += 2;
  i4d = 'integer man';
  i8 += 2;
  i8d = 'longlong man';
  f4 += 2.22;
  f4d = 'float man';
  f8 += 2.22;
  f8d = 'double man';
end-proc;

xjInData =
  '<?xml version="1.0"?>'
+ '<xmlservice>'
+ '<cmd error="fast" exec="cmd" var="chglibl">'
+ 'CHGLIBL LIBL('+TEST_LIB+')'
+ '</cmd>'
+ '<pgm error="fast" func="GETPACKED" name="TESTZSRV" var="packme">'

+ '<parm io="both" by="val" var="p8">'
+ '<data type="8a" var="i2d">1</data></parm>'

+ '<parm io="both" by="val" var="pp1">'
+ '<data type="4p2" var="pp">1</data></parm>'

+ '<parm io="both" by="val" var="pp2">'
+ '<data type="3p2" var="zz">1</data></parm>'

+ '<parm io="both" by="val" var="pp3">'
+ '<data type="12p2" var="zz">1</data></parm>'

+ '<parm io="both" by="val" var="pp4">'
+ '<data type="6p2" var="zz">1</data></parm>'

+ '<parm io="both" by="val" var="pp5">'
+ '<data type="8p2" var="zz">1</data></parm>'

+ '<parm io="both" by="val" var="pp6">'
+ '<data type="24p4" var="zz">1</data></parm>'

+ '<parm io="both" by="val" var="pp7">'
+ '<data type="48p8" var="zz">1</data></parm>'

+ '<parm io="both" by="val" var="p2">'
+ '<data type="15a" var="ppd">1</data></parm>'

+ '<parm io="both" var="p4">'
+ '<data type="30a" var="zzd">1</data></parm>'

+ '<parm io="both" by="val" var="p7">'
+ '<data type="5i0" var="i2">1</data></parm>'

+ '<parm io="both" var="p6">'
+ '<data type="30a" var="i1d">1</data></parm>'

+ '<parm io="both" by="val" var="p9">'
+ '<data type="10i0" var="i4">1</data></parm>'

+ '<parm io="both" by="val" var="p11">'
+ '<data type="20i0" var="i8">1</data></parm>'

+ '<parm io="both" by="val" var="p13">'
+ '<data type="4f" var="f4">1</data></parm>'

+ '<parm io="both" var="p14">'
+ '<data type="30a" var="f4d">1</data></parm>'

+ '<parm io="both" by="val" var="p15">'
+ '<data type="8f" var="f8">1</data></parm>'

+ '<parm io="both" var="p10">'
+ '<data type="30a" var="i4d">1</data></parm>'

+ '<parm io="both" var="p12">'
+ '<data type="30a" var="i8d">1</data></parm>'

+ '<parm io="both" var="p16">'
+ '<data type="30a" var="f8d">1</data></parm>'

+ '<parm io="both" by="val" var="p5">'
+ '<data type="3i0" var="i1">1</data></parm>'

+ '</pgm>'
+ '</xmlservice>'
+ x'00';

XMLSERVICE Interfaces

Goto Main Page

Who is this page for?

Instructions designed for IBM i developer exploring XMLSERVICE …

XMLSERVICE introduction

     -----------------------------------------
     |              Browser                  |
     |---------------------------------------|
(1)->| Download RPG (1) | Download PHP (2)   |
     | 1) XMLSERVICE    | a) PHP CW Toolkit  |<-(4) cw-tk-php-x.x.x.zip
     |    HTML/XML/REST | b) New PHP Toolkit |<-(3) cw-tk-php-x.x.x.zip
     |    no PHP        |--------------------|
     |    (xmlcgi.pgm)  | c) PHP “Raw XML”   |<-(2) Zend Server for IBM i or Linux or Windows
     |    (optional)    |   (ibm_db2, odbc)  |
     |                  |--------------------|
     | 2) XMLSERVICE DB2 stored procedures   |<-(1)
     |    (iPLUG4K, iPLUG32K, ..., iPLUG15M) |
     | 3) XMLSERVICE (xmlservice.pgm)        |<-(1) xmlservice-rpg-x.x.x.zip
     |    call most anything on IBM i ...    |
     |    (PGM, SRVPGM, PASE, DB2, etc.)     |
     -----------------------------------------
  1. XMLSERVICE is designed to allow you flexibility to develop from your laptop and run on your IBM i production with no changes. (RPG download)
  2. PHP DB2 stored procedure interface can be called form your laptop and run on your IBM i production with no changes. (PHP Zend Server)
  3. Zend New PHP Toolkit is one way to work with XMLSERVICE that ships with Zend Server for IBM i. (PHP download)
  4. A PHP old toolkit compatibility layer is also available for old PHP Zend toolkit users. (PHP download)

(3-4) You may update IBM i PHP toolkit via Zend PTF or see main page for update download.

Why do you call it XMLSERVICE??

  • same name as RPG source installation library (XMLSERVICE library)
  • name of the job(s)/program(s) seen in wrkactjob (see below)
  • services XML input scripts and returns XML output data (a rose by any other name)
wrkactjob

Opt  Subsystem/Job  User        Type  CPU %  Function        Status
      QSYSWRK        QSYS        SBS      .0                   DEQW
        XTOOLKIT     QTMHHTP1    BCH      .0  PGM-XMLSERVICE   SEMW

Note: XTOOLKIT naming 1.5+

Why is XMLSERVICE Open Source?

  • IBM has many nice folks that believe in Open Source for IBM i, sharing common utilities in IBM i community
  • Business friendly BSD license makes it easy to include in any Open Source project (Zend PHP, your applications, etc.)
  • XML documents over transport neutral design lends itself to “easy” inclusion is your proprietary/custom applications (XML is just a big string, you can use it anywhere)
  • Pure RPG source because many IBM i folks know the language, but not the tricks like using PASE for functions

XMLSERVICE highlights

  • 100% RPG code BSD licensed to be completely free to use (XMLSERVICE library download)
  • allows develop on PC and deploy on either PC (2-tier) or IBM i (1-tier)
  • all communication is XML documents
  • called RPG programs can maintain state, commit boundaries, etc
  • provided IPC mechanism that synchronizes web clients multiple sources
  • traditional DB2 connection security 1-2 tier (when using DB2 stored procedures provided)
  • conversions of XML string text to/from actual program required parameter geometery is handled (packed decimal 12p2, zoned decimal 7s2, integer 10i0/5i0, float 4f2/8f4, hex binary 400b), and character data converted automatically ASCII/EBCDIC (1024A/10a varying=’on’)
  • arrays of data structures are allowed both as paramters and return, including nested data structurers and/or arrays of data structures (try it to believe it)
  • Many limits of other toolkit PGM/SRVPGM callers are not an issue with XMLSERVICE (up to 15MB to date), therefore “old school” RPG using occurs can often pass back all data records in one call.

We have been experimenting with the following configurations.

1-tier (running on IBM i):
  1) HTML REST   : |PC|browser<----------------->|IBM i|<-ApacheCGI--|
  2) PHP ibm_db2 : |PC|browser<----------------->|IBM i|<-FastCGI/PHP|
  3) PHP pdo_ibm : |PC|browser<----------------->|IBM i|<-FastCGI/PHP|
  4) PHP odbc    : |PC|browser<----------------->|IBM i|<-FastCGI/PHP|
  5) Zend Toolkit: (using one of above transport interfaces)         | ...xml ...
                                                                    ->XMLSERVICE
                                                                     | ...call...
2-tier (Linux/Windows to IBM i):                                     |    >PGM
  1) PHP REST    : |PC|Apache/PHP<-REST--------->|IBM i|<-ApacheCGI--|    >SRVPGM
  2) PHP ibm_db2 : |PC|Apache/PHP<-DB2 Connect-->|IBM i|<-QRWTSRVR---|    >CMD
  3) PHP pdo_ibm : |PC|Apache/PHP<-DB2 Connect-->|IBM i|<-QRWTSRVR---|    >PASE shell
  4) PHP odbc    : |PC|Apache/PHP<-CA ODBC------>|IBM i|<-QRWTSRVR---|
  5) Zend Toolkit: (using one of above transport interfaces)

Note:

  • PHP was used to test, but any DB2/REST capable scripting language will work
  • REST interface used for test does not require PHP on IBM i (not fast)
  • ibm_db2 has been the best interface for production use (fast)
  • 2-tier does not require PHP on IBM i
  • odbc and pdo_ibm had trouble with large contiguous data (PDF)

XMLSERVICE syntax (XML in, XML out)

-----------------------------------------
|              Browser                  |
|---------------------------------------|
| Download RPG (1) | Download PHP (2)   |
| 1) XMLSERVICE    | a) PHP CW Toolkit  |
|    HTML/XML/REST | b) New PHP Toolkit |
|    no PHP        |--------------------|
|    (xmlcgi.pgm)  | c) PHP “Raw XML”   |
|    (optional)    |   (ibm_db2, odbc)  |
|    -----------------------------------|
| 2) XMLSERVICE DB2 stored procedures   |
|    (iPLUG4K, iPLUG32K, ..., iPLUG15M) |
| 3) XMLSERVICE (xmlservice.pgm)        |<-(1)
|    call most anything on IBM i ...    |
|    (PGM, SRVPGM, PASE, DB2, etc.)     |
-----------------------------------------
  1. XMLSERVICE parses and executes XML input for IBM i commands (CMD), programs (PGM/SRVPGM), and PASE utilites (ls, system, etc.), data results are then returned in XML output. XMLSERVICE processes XML scripts in “order of appearance”. Every effort is made to make one pass through the XML to keep performance, therefore XML input “order of appearance” is important and left up to the XML script writer (see below).
Order is important:
* <parm> followed by <return>
* <parm> followed by <overlay> (1.2.1 beta)
* <return> followed by <overlay> (1.2.1 beta)
* <ds> followed by <data>

Example:
<script>
<cmd>do something before PGM1</cmd>                         <--- action 1 call CMD1
<pgm name='PGM1'>                                           <--- action 2 call PGM1
<parm comment='parm 1'><ds><data>stuff</data></ds></parm>
<parm comment='parm 2'><ds><data>stuff</data></ds></parm>
<return comment='complex'><ds><data>stuff</data></ds></return>
</pgm>
<cmd>do something after PGM1</cmd>                          <--- action 3 call CMD2
<pgm name='PGM2'>                                           <--- action 4 call PGM2
<parm comment='parm 1'><ds><data>stuff</data></ds></parm>
<overlay comment='optional over parm 1'><ds><data>stuff</data></ds></parm>
<parm comment='parm 2'><ds><data>stuff</data></ds></parm>
<return comment='complex'><ds><data>stuff</data></ds></return>
<overlay comment='optional over return'><ds><data>stuff</data></ds></overlay>
</pgm>
<sh>do something PASE after PGM2</sh>                      <--- action 5 call PASE utility
</script>
  1. XMLSERVICE setting parameter data

Setting parameter data is a simple task that looks much the same as any language, using XML in this case. As you can see below you must send the entire XML document/script each time, because XMLSERVICE will not remember “everything” about each program (<pgm></pgm>), command (<cmd></cmd>) or PASE utility called (<sh></sh>).

1st XMLSERVICE call ...
<script>
<pgm name='PGM1'>
<parm comment='parm 1'><ds><data>stuff</data></ds></parm>
<parm comment='parm 2'><ds><data>stuff</data></ds></parm>
<return comment='complex'><ds><data>stuff</data></ds></return>
</pgm>
</script>

2nd XMLSERVICE call ...
<script>
<pgm name='PGM1'>
<parm comment='parm 1'><ds><data>different stuff</data></ds></parm>
<parm comment='parm 2'><ds><data>different stuff</data></ds></parm>
<return comment='complex'><ds><data>different stuff</data></ds></return>
</pgm>
</script>

Why send a full XML document description each time you call program/script?

Because XMLSERVICE is simply an script interpreter like BASIC or PHP, not a true compiler like RPG where “binary program” lasts forever, therefore you cannot just “pass XML data” without also describing what the data means (type=’12p2’).

If you are writing a language wrapper around this support (like Zend PHPToolkit), you can easily “hide” the XML document specifics of passing a full XML document (the whole idea). However, if you are using the RAW XML interface (this document), you will have to bite your tongue and send the whole document.

Note: However we are working short-cuts like overlay for some XML parameter “customization”, but we intend stopping short of complete “programming by XML” because we do not want to reinvent PHP (or the like).

Full example (2 complete calls):

1) XMLSERVICE call number 1 complete call with data XMLSERVICE will work:

<?xml version='1.0'?>
<script>
<pgm name='ZZCALLII'>
<parm  io='both'>
  <data type='1A' var='INCHARA'>a</data>
</parm>
<parm  io='both'>
  <ds>
  <data type='1A' var='INDS1.DSCHARA'>x</data>
  <data type='1A' var='INDS1.DSCHARB'>y</data>
  <data type='7p4' var='INDS1.DSDEC1'>66.6666</data>
  <data type='12p2' var='INDS1.DSDEC2'>77777.77</data>
  </ds>
</parm>
<return>
  <data type='10i0'>0</data>
</return>
</pgm>
</script>

2) XMLSERVICE call number 2 complete call with “different” data XMLSERVICE will work:

<?xml version='1.0'?>
<script>
<pgm name='ZZCALLII'>
<parm  io='both'>
  <data type='1A' var='INCHARA'>Z</data>
</parm>
<parm  io='both'>
  <ds>
  <data type='1A' var='INDS1.DSCHARA'>F</data>
  <data type='1A' var='INDS1.DSCHARB'>T</data>
  <data type='7p4' var='INDS1.DSDEC1'>1.1</data>
  <data type='12p2' var='INDS1.DSDEC2'>4.4</data>
  </ds>
</parm>
<return>
  <data type='10i0'>0</data>
</return>
</pgm>
</script>

XMLSERVICE REST interface (HTML/XML, no PHP)

      -----------------------------------------
      |              Browser                  |
      |---------------------------------------|
 (1)->| Download RPG (1) | Download PHP (2)   |
      | 1) XMLSERVICE    | a) PHP CW Toolkit  |
      |    HTML/XML/REST | b) New PHP Toolkit |
      |    no PHP        |--------------------|
      |    (xmlcgi.pgm)  | c) PHP “Raw XML”   |
      |    (optional)    |   (ibm_db2, odbc)  |
      |    -----------------------------------|
      | 2) XMLSERVICE DB2 stored procedures   |
      |    (iPLUG4K, iPLUG32K, ..., iPLUG15M) |
      | 3) XMLSERVICE (xmlservice.pgm)        |
      |    call most anything on IBM i ...    |
      |    (PGM, SRVPGM, PASE, DB2, etc.)     |
      -----------------------------------------

(1) HTTP REST xmlcgi.pgm (tests, demos):
http://myibmi/cgi-bin/xmlcgi.pgm&db2=x@uid=x@pwd=x@ipc=x@ctl=x@xmlin=x@xmlout=x

A example interface is included that does not require scripting language on the IBM i (no PHP).
The sample program xmlservice/xmlcgi is an RPG program that supports HTTP GET/POST interface.
This is an easy interface to call XMLSRVICE during testing without the bother of writing a script.

If you open up the source code of xmlcgi.pgm you will see that is calls the XMLSERVICE stored procedure interface in the next section. This allows for common access methodology including REST clients (like a browser), because no matter if calling from any application/device choice all security design remains within the consistent DB2 database connection(s). Also, because XMLSERVICE has a built in “one at a time” lock mechanism (semaphore) all/any clients can access the same XMLSERVICE job at the same time and you the script developer don’t have to worry about coordination.

REST parameters (xmlcgi.pgm)

  • db2 - what database (*LOCAL tested)
  • uid - user profile (*NONE - no uid version 1.5+)
  • pwd - profile password (*NONE - no password version 1.5+)
  • ipc - IPC key name/security route to XMLSERVICE job (/tmp/fred01, etc.)
  • ctl - CTL admin control XMLSERVICE job (see control below)
  • xmlin - XML input document (request)
  • xmlout - expected size of XML output document (response size in bytes)
Apache rest configuration (CGI interface)
Example: Add the following to /www/zendsvr/conf/httpd.conf
ScriptAlias /cgi-bin/ /QSYS.LIB/XMLSERVICE.LIB/
<Directory /QSYS.LIB/XMLSERVICE.LIB/>
  AllowOverride None
  order allow,deny
  allow from all
  SetHandler cgi-script
  Options +ExecCGI
</Directory>

Optional for server side includes (very handy for pure html/xml sites):
# server side includes
# AddOutputFilter INCLUDES .htm
# AddOutputFilter INCLUDES .html
Options +ExecCGI +Includes
AddType text/html .shtml
AddOutputFilter INCLUDES .shtml
AddOutputFilter INCLUDES .html

Example (html interface xmlcgi.pgm):
<html>
<body>
<!-- call XMLSERVICE/ZZCALL(a:b:11.1111:222.22:record) (post or get) -->
<form name="input" action="/cgi-bin/xmlcgi.pgm" method="post">
<input type="hidden" name="db2" value="*LOCAL">
<input type="hidden" name="uid" value="*NONE">
<input type="hidden" name="pwd" value="*NONE">
<input type="hidden" name="ipc" value="/tmp/rangerhtml">
<input type="hidden" name="ctl" value="*none">
<input type="hidden" name="xmlin"
value="<?xml version='1.0'?>
<pgm name='ZZCALL' lib='XMLSERVICE'>
<parm><data type='1A'>a</data></parm>
<parm><data type='1A'>b</data></parm>
<parm><data type='7p4'>11.1111</data></parm>
<parm><data type='12p2'>222.22</data></parm>
<parm>
  <ds>
  <data type='1A'>x</data>
  <data type='1A'>y</data>
  <data type='7p4'>66.6666</data>
  <data type='12p2'>77777.77</data>
  </ds>
</parm>
<return><data type='10i0'>0</data></return>
</pgm>">
<input type="hidden" name="xmlout" value="32768">
<input type="submit" value="Submit" />
</form>
</body>
</html>

Output:
<?xml version='1.0'?>
<pgm name='ZZCALL' lib='XMLSERVICE'>
<parm><data>C</data></parm>
<parm><data>D</data></parm>
<parm><data>321.1234</data></parm>
<parm><data>1234567890.12</data></parm>
<parm>
  <ds>
  <data>E</data>
  <data>F</data>
  <data>333.3330</data>
  <data>4444444444.44</data>
  </ds>
</parm>
<return><data>0</data></return>
</pgm>

Note: uid / pwd *NONE available XMLSERVICE version 1.5+.

XMLSERVICE stored procedure interface (production)

 -----------------------------------------
 |              Browser                  |
 |---------------------------------------|
 | Download RPG (1) | Download PHP (2)   |
 | 1) XMLSERVICE    | a) PHP CW Toolkit  |
 |    HTML/XML/REST | b) New PHP Toolkit |
 |    no PHP        |--------------------|
 |    (xmlcgi.pgm)  | c) PHP “Raw XML”   |
 |    (optional)    |   (ibm_db2, odbc)  |
 |    -----------------------------------|
 | 2) XMLSERVICE DB2 stored procedures   |<-(2)
 |    (iPLUG4K, iPLUG32K, ..., iPLUG15M) |
 | 3) XMLSERVICE (xmlservice.pgm)        |
 |    call most anything on IBM i ...    |
 |    (PGM, SRVPGM, PASE, DB2, etc.)     |
 ------------------------------------------

(2) DB2 stored procedures (production):
call iPLUG[R]xxx(ipc, ctl, xmlin, [xmlout])

This DB2 stored procedure interface was designed for production level machines Zend Server on i (1 tier)
and remote machines over DB2 Connect (2 tier).

When you first start using XMLSERVICE generic stored procedures (see below), you may wonder why not just write custom stored procedures for each program i wish to call vs. using XMLSERVICE “dynamic stored procedures”?

  • XML documents are very easy to send around over any transport, therefore you can upgrade your application capabilities instantley without all the custom stored procedure programming, In a practical sense this means you can use different scripting languages (php, java, perl, ruby, etc.), with various database connections (db2 connect, odbc, etc.) and/or simply HTTP (seen previously)
  • XMLSERVICE supports full state programming via IPC routing (/tmp/fred1, /tmp/sally2, etc.), thereby traditional RPG programming techniques with many open files, transactions, etc., can be employed in called programs/modules lowering the learning curve (and rewrites not needed)
  • XMLSERVICE XML documents are very easy to wrapper with any custom scripting interface, such as the new PHP toolkit interface Zend is working on.
DB2 in/out parameters (connections supporting in/out parameters):
... sizes: 4K, 32K, 65K, 512K, 1M, 5M, 10M up to 15M  (see crtsql in download) ...
iPLUG4K(IN IPC CHAR(1024), IN CTL CHAR(1024),IN CI CHAR(4064), OUT C0 CHAR(4064))
iPLUG32K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(32000), OUT CO CLOB(32000))
iPLUG65K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(65K), OUT CO CLOB(65K))
iPLUG512K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(512K), OUT CO CLOB(512K))
iPLUG1M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(1M), OUT CO CLOB(1M))
iPLUG5M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(5M), OUT CO CLOB(5M))
iPLUG10M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(10M), OUT CO CLOB(10M))
iPLUG15M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(15M), OUT CO CLOB(15M))

  $stmt = db2_prepare($conn, "call XMLSERVICE.iPLUG4K(?,?,?,?)");
  $ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
  $ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
  $ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
  $ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
  $ret=db2_execute($stmt);


DB2 Result set returned (connections not supporting in/out parameters):
... sizes: 4K, 32K, 65K, 512K, 1M, 5M, 10M up to 15M (see crtsql in download) ...
CREATE PROCEDURE XMLSERVICE.iPLUGR4K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CHAR(4096))
iPLUGR32K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CHAR(32000))
iPLUGR65K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(65K))
iPLUGR512K(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(512K))
iPLUGR1M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(1M))
iPLUGR5M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(5M))
iPLUGR10M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(10M))
iPLUGR15M(IN IPC CHAR(1024), IN CTL CHAR(1024), IN CI CLOB(15M))

  $stmt = db2_prepare($conn, "call $libxmlservice.iPLUGR4K(?,?,?)");
  $ret=db2_execute($stmt,array($ipc,$ctl,$clobIn));
  while ($row = db2_fetch_array($stmt)){
    $clobOut .= trim($row[0]);
  }

Why different sizes??

The idea is simply pick the “best fit” max size of XML documents to/from IBM i per call, so you don’t transport a whole bunch of blanks between you scripting/IBM i applications.

Stored procedure parameters (details follow):

  • IN IPC CHAR(1024) - IPC key name/security
  • IN CTL CHAR(1024) - CTL admin control XMLSERVICE job
  • IN CI CLOB(15M) - XML input document (request)
  • OUT CO CLOB(15M) - XML output document (response)

Note: iPLUGRxxx procedures return a result set that is collected by fetch.

1) IN IPC CHAR(1024) - IPC key (InternalKey)

The IPC/InternalKey is used to route XML input/output documents to the correct XMLSERVICE job. The IPC is fully qualified path commonly in the /tmp directory where each unique path is the target of given XMLSERVICE job being used by a given user/password profile (normal profile authority applies).

  1. InternalKey must be a fully IFS qualified path, if not exist then XMLSERVICE will attempt to create on first use.
  2. InternalKey object authorization is standard IBM i profile mechanism.
  3. XMLSERVICE provides a sync IPC lock (/tmp/$anything), so only one user/request is active at a time (like 5250).

Example call PGM ZZCALL, run in Zend subsystem, *debug wait before call (qsysopr msg):

$ipc='/tmp/ranger001';
$ctl = '*sbmjob(ZENDSVR/ZSVR_JOBD) *debug';
$clobIn =
"<?xml version="1.0"?>
 <script>
  <cmd>ADDLIBLE LIB(XMLSERVICE) POSITION(*FIRST)</cmd>
  <pgm name='ZZCALL'>
   <parm>
    <data type='1A'>a</data>
   </parm>
   <return>
    <data type='10i0'>0</data>
   </return>
  </pgm>
 </script>";
$clobOut = "";
$stmt = db2_prepare($conn, "call XMLSERVICE.iPLUG4K(?,?,?,?)");
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);

2) IN CTL CHAR(1024) - CTL admin

These control options are used to control the XMLSERVICE jobs.

// *immed              - end server immed destroy ipc
// *sbmjob[(lib/jobd)] - sbmjob(PLUGJOBLIB/PLUGJOBD)
// *nostart            - disallow spawn
// *here               - run stateless in stored proc job
// *session            - retrieve session key (IPC)
// *license            - license this code
:

Example kill XMLSERVICE for ‘/tmp/rangerusr’:

$ipc='/tmp/rangerusr';
$ctlKill="*immed";
$clobInKill = '<?xml version="1.0"?>';
$sql = "call $libxmlservice.iPLUGR4K('$ipc','$ctlKill','$clobInKill')";
$ret=db2_exec($conn,$sql);

3) IN CI CLOB(4K to 15M) - XML input

This is the input XML request document (see XML syntax next section). The XML input document will run all actions in the XMLSERVICE job and produce an XML output document.

<?xml version="1.0"?>
<script>
<cmd comment='addlible'>ADDLIBLE LIB(DB2) POSITION(*FIRST)</cmd>
<pgm name='ZZSRV' func='ZZARRAY'>
<parm comment='search this name'>
  <data var='myName' type='10A'>Ranger</data>
</parm>
<parm  comment='max allowed return'>
  <data var='myMax' type='10i0'>5</data>
</parm>
<parm  comment='actual count returned'>
  <data var='myCount' type='10i0' enddo='mycount'>0</data>
</parm>
<return>
  <ds var='dcRec_t' dim='10' dou='mycount'>
    <data var='dcMyName' type='10A'>na</data>
    <data var='dcMyJob' type='4096A'>na</data>
    <data var='dcMyRank' type='10i0'>0</data>
    <data var='dcMyPay' type='12p2'>0.0</data>
  </ds>
</return>
</pgm>
</script>

4) OUT CO CLOB(15M) - XML output

The output XML document will be produced in the same order that the XML input script runs, The output can be collected from the in/out parameter (iPLUGxxx) or from fetch of a result set (iPLUGRxxx).

<?xml version="1.0"?>
<script>
<cmd comment='addlible'>+++ success ADDLIBLE LIB(XMLSERVICE) POSITIO</cmd>
<pgm name='ZZSRV' lib='XMLSERVICE' func='ZZARRAY'>
<parm comment='search this name'>
  <data var='myName'>Ranger</data>
</parm>
<parm comment='max allowed return'>
  <data var='myMax'>5</data>
</parm>
<parm comment='actual count returned'>
  <data var='myCount'>5</data>
</parm>
<return>
  <ds var='dcRec_t'>
    <data var='dcMyName'>Ranger1</data>
    <data var='dcMyJob'>Test 101</data>
    <data var='dcMyRank'>11</data>
    <data var='dcMyPay'>13.42</data>
  </ds>
<ds var='dcRec_t'>
    <data var='dcMyName'>Ranger2</data>
    <data var='dcMyJob'>Test 102</data>
    <data var='dcMyRank'>12</data>
    <data var='dcMyPay'>26.84</data>
  </ds>
<ds var='dcRec_t'>
    <data var='dcMyName'>Ranger3</data>
    <data var='dcMyJob'>Test 103</data>
    <data var='dcMyRank'>13</data>
    <data var='dcMyPay'>40.26</data>
  </ds>
<ds var='dcRec_t'>
    <data var='dcMyName'>Ranger4</data>
    <data var='dcMyJob'>Test 104</data>
    <data var='dcMyRank'>14</data>
    <data var='dcMyPay'>53.68</data>
  </ds>
<ds var='dcRec_t'>
    <data var='dcMyName'>Ranger5</data>
    <data var='dcMyJob'>Test 105</data>
    <data var='dcMyRank'>15</data>
    <data var='dcMyPay'>67.10</data>
  </ds>
</return>
</pgm>
</script>

Workaround for “bad” drivers leaving junk back of output clob

Alas, the world is not perfect 1-2 tier DB2 drivers IBM i, Windows, Linux, etc., so occasionally a “hack” is handy.

I always “scope” my XML input requests with <script>…</script>, so anything past tailing </script> is ‘junk’ (errors return as <report>…</report>).

The new XMLSERVICE keyword *hack adds </hack> back of every record return result set can be very useful for drivers that do not support stored procedure in/out parameters like PHP odbc.

function driverJunkAway($xml)
{
  // trim blanks (opps no)
  $clobOut = $xml;
  if (! trim($clobOut)) return $clobOut;

  // result set has extra data (junk)
  $fixme = '</hack>';
  $pos = strpos($clobOut,$fixme);
  if ($pos > -1) {
    $clobOut = substr($clobOut,0,$pos);
  }
  else {
    $fixme = '</script>';
    $pos = strpos($clobOut,$fixme);
    if ($pos > -1) {
      $clobOut = substr($clobOut,0,$pos+strlen($fixme));
    }
    // maybe error/performance report
    else {
      $fixme = '</report>';
      $pos = strpos($clobOut,$fixme);
      if ($pos > -1) {
        $clobOut = substr($clobOut,0,$pos+strlen($fixme));
      }
    }
  }
  return $clobOut;
}

Example PHP client use driverJunkAway($xml)

function xmlservice_ibm_db2($xml) {
global $fast, $db, $user, $pass, $ipc, $ctl, $conn, $lib, $plug, $plugR;
  $xmlIn = $xml;
  $xmlOut = '';
  if (!$conn) {
    if ($fast) $conn = db2_pconnect($db, $user, $pass);   // persistent/pooled connection
    else $conn = db2_connect($db, $user, $pass);          // full open/close connection
  }
  if (!$conn) die("Bad connect: $db, $user");
  $stmt = db2_prepare($conn, "call $lib.$plug(?,?,?,?)"); // Call XMLSERVICE
                                                          // stored procedure interface
                                                          // in/out parameter (xmlOut)
                                                          // sizes: iPLUG4K - iPLUG15M
  if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
  $ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);     // ? - /tmp/raw_$user (*sbmjob)
  $ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);     // ? - *here or *sbmjob
  $ret=db2_bind_param($stmt, 3, "xmlIn", DB2_PARAM_IN);   // ? - XML input script
  $ret=db2_bind_param($stmt, 4, "xmlOut", DB2_PARAM_OUT); // ? - XML output return
  $ret=db2_execute($stmt);
  if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
  return driverJunkAway($xmlOut);                         // just in case driver odd
                                                          // ... possible junk end record,
                                                          // record</script>junk
}

function xmlservice_odbc($xml) {
global $fast, $db, $user, $pass, $ipc, $ctl, $conn, $lib, $plug, $plugR;
  $xmlIn = $xml;
  $xmlOut = '';
  if (!$conn) {
    if ($fast) $conn = odbc_pconnect($db, $user, $pass);  // persistent/pooled connection
    else $conn = odbc_connect($db, $user, $pass);         // full open/close connection
  }
  $stmt = odbc_prepare($conn, "call $lib.$plugR(?,?,?)"); // Call XMLSERVICE
                                                          // stored procedure interface
                                                          // result set return (fetch)
                                                          // sizes: iPLUGR4K - iPLUGR15M
  if (!$stmt) die("Bad prepare: ".odbc_errormsg());
  $options = array($ipc,                                  // ? - /tmp/raw_$user (*sbmjob)
                  $ctl,                                  // ?- *here or *sbmjob
                  $xmlIn);                               // ?- XML input script
  // bad behavior odbc extension
  // ... IBM i result set warning???
  error_reporting(~E_ALL);                                // bad behavior odbc extension
                                                          // ... IBM i result set warning???
  $ret=odbc_execute($stmt,$options);
  if (!$ret) die("Bad execute: ".odbc_errormsg());
  error_reporting(E_ALL);
  while(odbc_fetch_row($stmt)) {
    $xmlOut .= driverJunkAway(odbc_result($stmt, 1));     // bad behavior odbc extension
                                                          // ... possible junk end record,
                                                          // xmlservice provided $ctl='*hack'
                                                          // record</hack>junk
  }
  return $xmlOut;
}

function xmlservice_pdo_ibm($xml) {
global $fast, $db, $user, $pass, $ipc, $ctl, $conn, $lib, $plug, $plugR;
  $xmlIn = $xml;
  $xmlOut = '';
  if (!$conn) {
    $database = "ibm:".$db;
    try {
      if ($fast) $opt = array(PDO::ATTR_PERSISTENT=>true, PDO::ATTR_AUTOCOMMIT=>true);  // persistent/pooled connection
      else $opt = array(PDO::ATTR_AUTOCOMMIT=>true);                                    // full open/close connection
      $conn = new PDO($database, strtoupper($user), strtoupper($pass), $opt);
      if (!$conn) throw new Exception("Bad");
    } catch( Exception $e ) {
      die("Bad connect: $database, $user");
    }
  }
  try {
    $stmt = $conn->prepare("call $lib.$plug(?,?,?,?)");   // Call XMLSERVICE
                                                          // stored procedure interface
                                                          // in/out parameter (xmlOut)
                                                          // sizes: iPLUG4K - iPLUG15M
    if (!$stmt) throw new Exception('Bad');
  } catch( Exception $e ) {
    $err = $conn->errorInfo();
    $cod = $conn->errorCode();
    die("Bad prepare: ".$cod." ".$err[0]." ".$err[1]." ".$err[2]);
  }
  try {
    $r1=$stmt->bindParam(1,$ipc, PDO::PARAM_STR);
    $r2=$stmt->bindParam(2,$ctl, PDO::PARAM_STR);
    $r3=$stmt->bindParam(3,$xmlIn, PDO::PARAM_STR);
    $r4=$stmt->bindParam(4,$xmlOut, PDO::PARAM_STR|PDO::PARAM_INPUT_OUTPUT);
    $ret = $stmt->execute();
    if (!$ret) throw new Exception('Bad');
  } catch( Exception $e ) {
    $err = $stmt->errorInfo();
    $cod = $stmt->errorCode();
    die("Bad execute: ".$cod." ".$err[0]." ".$err[1]." ".$err[2]);
  }
  return driverJunkAway($xmlOut);                         // just in case driver odd
                                                          // ... possible junk end record,
                                                          // record</script>junk
}

XMLSERVICE advanced CCSID

Using default PHP toolkit DB2 clob interface (iPLUGxxx/iPLUGRxxx), ccsid conversion occurs naturally as DB2 client/server and you will not have to code before/after, but method is available if you have a specific concern or you have scripts returning many different languages.

Example:

<data type='200A' before='819/424' after='424/819'>bin2hex('Hebrew_ascii_raw_chars')</data>
<data type='200A' before='819/1098' after='1098/819'>bin2hex('Farsi_ascii_raw_chars')</data>
<data type='200A' before='819/880' after='880/819'>bin2hex('Russia_ascii_raw_chars')</data>
<data type='200A' before='819/280' after='280/819'>bin2hex('Italy_ascii_raw_chars')</data>
<data type='200A' before='819/273' after='273/819'>bin2hex('Germany_ascii_raw_chars')</data>
<data type='200A' before='819/1088' after='1088/819'>bin2hex('Korea_ascii_raw_chars')</data>
<data type='200A' before='1208/13488' after='13488/1208'>bin2hex('Japan_ascii_raw_chars')</data>
<data type='200A' before='1208/13488' after='13488/1208'>bin2hex('China_ascii_raw_chars')</data>
where:
before    - XMLSERVICE convert CCSID before ILE program call
after     - XMLSERVICE convert CCSID after ILE program call for client return
bin2hex() - script hex string unaltered ascii image (also returned hex string avoid any conversion)
pack()    - script uses pack('H*',"xml_hex_back") function in PHP program for ascii characters
Note:
Up to four conversions can take place for the truly diabolical ccsid issues
<data type='A' before='cc1/cc2/cc3/cc4' after='cc4/cc3/cc2/cc1'>bin2hex('wild_ascii_raw_chars')</data>
flow:
-> PHP client bin2hex('wild_ascii_raw_chars')
-> xmlservice hex2bin back to 'wild_ascii_raw_chars'
-> xmlservice convert cc1->cc2->cc3->cc4 (before)
-> xmlservice make ILE call
-> xmlservice convert cc4->cc3->cc2->cc1 (after)
-> xmlservice tohex "xml_hex_back"
-> PHP client $chars = pack('H*',"xml_hex_back")

XMLSERVICE Examples

Goto Main Page

The following foils include running examples you can click to help get started as you learn about this Open Source.

Goal of Open Source XMLSERVICE RPG library is flexibility, enabling ANY sort of transport local/remote connection with any language available in your enterprise to call resources on your IBM i machine. XMLSERVICE primary transport interface in production today is DB2 connections using included stored procedures (xmlstoredp.srvpgm iPLUGxxxx). XMLSERVICE secondary transport interface used mostly demos is Apache REST connection (xmlcgi.pgm). However, XMLSERVICE allows writing your own custom transport (anything you imagine). XML Service and Toolkit on-going mission is to add new function, improve performance, expand uses, with goal of never impacting existing customers.

Warning: This is an active IBM i education machine, occasionally examples may not work, try back later.

Part 1 - Protect your investment

  • XML Service Access Native IBM i Objects from any language using XML
  • XML Service completely free/safe commercial use download (Github)
  • XML Service examples HTML/XML (no PHP, no RPG)
  • XML Service examples RPG (no PHP)
  • XML Service vs. DB2 Stored Procedures - Why use XML Service at all?
  • XML Service protect your investment summary

XML Service - is free

  • XML Service is completely free/safe commercial use
    • BSD license, download, keep any source copy forever (safe)
  • XML Service written in RPG open source (you can change it)
    • Techniques used IBM i calls are stable, unlikely to change (ever)
  • XML Service is supported Open Source
    • XML Service fix/add improvement goal is never impact current customers

XML Service - XML everything

  • XML input <> XML output
    • Any device
    • Any language
    • Any local/remote connection
    • Any IBM i service (PGM, CMD, System API, DB2, PASE)

XML Service - IBM i Native Access through XML

  • Access Native IBM i Objects from any language using XML
    • Local or remote IBM i: IBM i local, IBM i-2-IBM i, Linux/Unix/Windows-2-IBM, etc.
    • Any language access: PHP, Ruby, Java, Perl, RPG, no language at all (HTML/XML)
    • Many Native Object Types: DB2 SQL and Native, Program call, Procedure call, Data Area, Data Queue, Message Queue, Commands, System values, Spool files, PASE utilities
  • Local or remote call interfaces to XMLSERVICE (included RPG download)
    • Primary: call DB2 stored procedures local or remote (iPLUG4K - iPLUG15M)
    • Secondary: call REST HTTP local or remote (xmlcgi.pgm)

XML Service - Moving Parts

  • Any language
    • Browser HTML/XML
    • Script PHP, Ruby, JavaScript, etc.
    • Compiled RPG, C, etc.
  • Any local/remote connection
    • Linux/Unix/Windows/Mac ibm_db2/odbc/REST to IBM i
    • Native IBM i ibm_db2/odbc/REST to IBM i

XML Service public “stateless” (*here)

  • XMLSERVICE public “stateless” (CTL=’*here’, IPC=’*NA’)
    • profile FRED (any public QSQ)
    • profile SALLY (any public QSQ)
    • profile RITA (any public QSQ)
    • profile XAVIER (any public QSQ)
  • XMLSTOREDP->XMLSERVICE (QSQ)
    • QSQ temporary profile use (stateless)
    • QSQ return to pool on script end
    • XMLSERVICE restart every request (web style)

XML Service private “state full” (*sbmjob)

  • XMLSERVICE private “state full” (CTL=’*sbmjob’, IPC=’/tmp/xxxx’)
    • profile FRED XTOOLKIT myjob1,myjob2 (private)
    • profile SALLY XTOOLKIT sallyjob1 (private)
    • profile RITA XTOOLKIT nursejob (private)
    • profile XAVIER XTOOLKIT xjob1,xjob2,xjob3,xjob4,xjob5 (private)
  • XMLSTOREDP (QSQ)
    • QSQ temporary profile use (stateless)
    • QSQ return to pool on script end
  • XMLSERVICE (XTOOLKIT)
    • XTOOLKIT owned by profile (private)
    • XTOOLKIT job never ends (until killed)
    • XTOOLKIT full state programming (5250 style)

XML Service Configuration

  • Apache REST (xmlcgi.pgm)
  • DB2 stored procedures (xmlstoredp.srvpgm iPLUGxx)

XML Service - Example HTML/XML (no PHP, no RPG)

  • XML Service HTTP browser direct (xmlcgi.pgm)

    • web enable via httpd.conf
    <form method='POST' action='/cgi-bin/xmlcgi.pgm'>
    ScriptAlias /cgi-bin/ /QSYS.LIB/XMLSERVICE.LIB/
    <Directory /QSYS.LIB/XMLSERVICE.LIB/>
      order allow,deny
      allow from all
      SetHandler cgi-script
      Options +ExecCGI
    </Directory>
    

XML Service - Example RPG (no PHP)

  • XML Service stored procedure interface
    1. RPG DB2 using Exec Sql (iPLUGxxx)
    2. RPG DB2 using CLI (iPLUGxxx)
  • web enable via httpd.conf
# demo callpase
ScriptAlias /demo/ /QSYS.LIB/CALLPASE.LIB/
<Directory /QSYS.LIB/CALLPASE.LIB/>
  order allow,deny
  allow from all
  SetHandler cgi-script
  Options +ExecCGI
</Directory>

Note: Exec Sql RPG CGI uses profile QTMHHTP1, however PHP is usually running QTMHHTTP, so you may fail authorisation sharing same XMLSERVICE job (1). Therefore, i recommend use RPG CLI technique to run any profile, where XMLSERVICE job(s) PHP/RPG is no problem (2).

XML Service vs. DB2 Stored Procedures - Why use XML Service at all?

Why not write my own stored procedures? Why use XML Service at all?

XML Service Stored procedure
Device, protocol, & language portability
browser, REST, DB2, RPG… device/driver/language specific
Complex Data Structures
trivial near impossible
Call CMDs and collect data
trivial very difficult
Call PASE utilities and collect data
trivial very difficult
Route same job (IPC/internalKey)
trivial near impossible
30,000 records around 2 seconds
fast faster

Protect your investment

  • XML Service protects your wallet 100% free download
  • XML Service protects your skills 100% RPG source code
  • XML Service protects your project costs with XML based low budget features
  • XML Service protects your investment applications functions/performance over time
  • XML Service protects your investment across device proliferation
  • XML Service protects your investment across script language proliferation
  • XML Service protects your investment across any transport driver (XML is a string)

Part 2 - Production use today

Topics

  • PHP Toolkit included with Zend Server for IBM i
  • XML Interface ibm_db2, pdo_ibm, odbc included with Zend Server for IBM i
  • XML Interface and PHP Toolkit - Performance
  • XML Interface and PHP Toolkit - Debugging
  • XML Interface and PHP Toolkit - Active community/support

New PHP Toolkit included with Zend Server for IBM i

  • Zend Server PHP Toolkit (included)
    • PHP CW Layer - old toolkit
    • PHP New Toolkit - OO toolkit

XML Interface ibm_db2, pdo_ibm, odbc included with Zend Server for IBM i

  • XML Service stored procedure interface (included)
    • DB2 ibm_db2 param in/out (iPLUG 4k-15m)
    • DB2 odbc result set (iPLUGR 4k-15m)

XML Interface and PHP Toolkit - Active community/support

You are not alone, seek out help …

Debug technique: It’s as easy as 1-2-3-4-5-6-7-8-9 :)

  1. Run the test script that contains control “*debug” and script will “hang” while it waits on #2
$ctl .= "*debug";
  1. A MSGW inquiry message in DSPMSG QSYSOPR will be generated by the toolkit. Note the job information (number, name, user) provided in the MSGW.
  2. STRSRVJOB using that job information as parameters.
  3. STRDBG with the program and library you wish to debug.
  4. Answer the MSGW. Any answer will do–“G” is fine.
  5. The RPG program source will appear in debug mode in your terminal, ready to step through, allowing you to inspect variables, etc.
  6. When done inspecting and stepping, let the RPG program complete (using function keys indicated on screen).
  7. ENDDBG
  8. ENDSRVJOB

Other debug options …

          Job1 (threaded)   Job 2                        Job 3 (DB2 userid/password)    Job 4 (optional XTOOLKIT job)
                            (ctl=*debugcgi)              (ctl=*debugproc)                (ctl=*debug)
browser -> Apache          ->XMLCGI (Apache CGI child) -> QSQSRVR (XMLSERVICE *here)
                                                      -> QSQSRVR (XMLSERVICE client) -> XTOOLKIT (XMLSERVICE ipc=/tmp/flinstone)

$ctl .= " *debugcgi";  // Job 2 - debug XMLCGI to see REST/HTTP data passed by client (when using REST only)
$ctl .= " *debugproc"; // Job 3 - debug XMLSERVICE "client" to see DB2 passed data (DB2 interface)
$ctl .= " *debug";     // Job 4 - debug XMLSERVICE "server" to see XMLSERVICE calls (DB2 interface)
                      // Note:   when ctl='*here', both XMLSERVICE "client"/"server"
                      //         are in QSQSRVSR job (NO XTOOLKIT job)
                      // remote: Attaching with LUW drivers changes QSQSRVR ...
                      //   CLIENT (Client Access drivers) <==> QZDAxxxx
                      //   CLIENT (DB2 Connect drivers)   <==> QRWxxxx

XMLSERVICE Layouts

Goto Main Page

This page is for RPG developers that want to customize XMLSERVICE.

XMLSERVICE source files

     -----------------------------------------
     |              Browser                  |
     |---------------------------------------|
(1)->| Download RPG (1) | Download PHP (2)   |
     | 1) XMLSERVICE    | a) PHP CW Toolkit  |
     |    HTML/XML/REST | b) New PHP Toolkit |
     |    no PHP        |--------------------|
     |    (xmlcgi.pgm)  | c) PHP “Raw XML”   |
     |    (optional)    |   (ibm_db2, odbc)  |
     |    -----------------------------------|
     | 2) XMLSERVICE DB2 stored procedures   |<-(1)
     |    (iPLUG4K, iPLUG32K, ..., iPLUG15M) |
     | 3) XMLSERVICE (xmlservice.pgm)        |<-(1) xmlservice-rpg-x.x.x.zip
     |    call most anything on IBM i ...    |
     |    (PGM, SRVPGM, PASE, DB2, etc.)     |
     -----------------------------------------

The following is a brief description of the function of the source files included with the XMLSERVICE library Open Source project.

LIB:
XMLSERVICE

QSQLSRC:
crtsql - db2 utility script to create stored procedures

QRPGLESRC:
plugconf_h - compile configuration options
plugmri_h - translateable messages
plugcach_h - interface for all cahces
plugcach - implementation for all caches
plugbug_h - interface debug (*debug)
plugbug - implement debug message qsysopr
plugerr_h - interface errors
plugerr - implement error collection and report
plugile_h - ILE ABI interface
plugile - implement ILE ABI paramter memory layout
plugipc_h -interface IPC (*nostart)
plugipc - implement IPC shared memory and semaphore
pluglic_h - interface license (*license)
pluglic - implement license report
plugpase_h - interface PASE functions
plugpase - implement PASE PGM, SRVPGM, sh
plugperf_h - interface performance (*rpt)
plugperf - implement performance collection and report
plugrun_h - interface run loop (*here *immed *justproc *sbmjob)
plugrun - implement client/server run loop (RPG cycle)
plugxml_h - interface XML
plugxml - implement XML parse and run logic
plugsql_h - interface sql by XML
plugsql - implement sql by XML (1.5)
plugdb2_h - interface sql db2 by XML
plugdb2 - implement sql db2 by XML
plugsig_h.rpgle - interface timeout signal (1.6.6)
plugsig.rpgle - implement timeout signal
plugconv_h.rpgle - interface ccsid conversion (1.6.6)
plugconv.rpgle - implement ccsid conversion
xmlcgi - PGM client interface Apache CGI (HTTP REST)
xmlmain - PGM client interface RPG-2-RPG (experimental)
xmlservice - PGM server deamons (XMLSERVICE jobs)
xmlstoredp - SRVPGM client DB2 stored procedures (DB2 connections)
zzcall - optional PGM for pear tests
zzsrv - optional SRVPGM for pear tests

XMLSERVICE hooks plugconf

XMLSERVICE allows for you to customize a few key security and output decisions in the RPG module plugconf or provide your own plugconf(x) version.

XMLSERVICE hooks APIs plugconf_h

The hooks are ONLY available with XMLSERVICE version 1.5.5+.

plugconf module must implement the following interfaces prototyped by plugconf_h … failure to implement these APIs will result in compile failure.

There are key hooks in plugrun / xmlcgi that allow your to decline service after you snoop the input XML. Likewise, there are key hooks that allow you to manipulate the XML output returned to the clients if you wish to “customize” XML (cool), however it should be noted that if you change the output XML you may break other clients (like Zend PHP wrapper).

Running with *NONE from XMLCGI

XMLSERVICE via XMLCGI may be too powerful using only HTML/XML with no user/password (*NONE), so it is turned OFF by default always. However, if your machine is behind a firewall and you trust your team you can enable *NONE …

  1. make your own plugconf(x)
  2. D PLUGNONEOK      S              1N   inz(*ON)
  3. recompile and your machine will be wide open …

Note: The other setting PLUGDEMOOK is not used is future consideration for demos not from XMLCGI.

XMLSERVICE XMLCGI (RPG CGI)

A sample XMLCGI interface was included to allow REST calls to XMLSERVICE. This was very handy to check out XML to/from XMLSERVICE before ever committing pen to paper on an actual script like PHP. It also seems to very handy fro one off HTML/XML applications that can reside on your laptop (or other device) and use the built in browser to run IBM i.

BIG DESIGN HINT:

Although common practice in IBM i circles, I choose not to use Basic Authentication and/or “switch profile” in RPG CGI programs on the web (httpd.conf ServerId). I much prefer to leave all Apache/CGI related jobs as neutral “low authority” like QTMHHTTP (IBM i) or NOBODY (Linux) for clearly understood security. Also, I prefer keeping any security “switch profile” activity in the database connection (DB2) for portability of design and multiple client support (1-tier/2-tier). An example of this “database security” style of CGI is Apache module XMLCGI included in XMLSERVICE download (main page). XMLCGI implements all “security” preferences and “switch profile” using the DB2 CLI interface in RPG.

Briefly, reasons i like keep the “profile security” in the database … * portable design idea in tradition of millions of PHP/MySql web sites (idea of profile is in database) * easy switch/sharing between 1-tier and 2-tier applications across common database design (DB2) * no “protocol” invention required because most languages support database (including PHP, Java, RPG, Cobol, etc.) * supporting/sharing applications a wide range of device clients such as PDAs, laptops, servers is much easier as most can handle REST and/or DB2 communications (whatever language is available)

Do it yourself RPG:

  • If you are a do it yourself RPG programmer, you may want to download and study XMLCGI RPG source file included as this would allow you to write any RPG front-end (just steal the DB2 CLI stuff in XMLCGI if you want a server with profiles).
    • RPG program XMLCGI is a traditional “Apache/Unix” style CGI server (ala PHP/MySql), therefore you do not need “switch profile” in the XMLCGI job. All/any “switch profile” is occurring at the DB2 database layer via DB2 CLI server mode not the Apache level (without using profiles in httpd.conf)
    • WARNING: Unless https, XMLCGI sends unencrypted password around web and/or leaves them in files (unless using *NONE), which is a bad idea of course, but this is a demonstration module for testing (without PHP, etc.), you as the RPG programmer are intended to customize to your production world (Open Source RPG code).

XMLSERVICE XMLMAIN (unused)

Also included, is a simple RPG program XMLMAIN that calls client interface XMLSERVICE in processes inside or outside web context, has not been extensively tested (very little in fact), but should be general idea for a IBM i RPG centric interface (if such a thing is even relevant these Internet days).

If you wish to build a traditional web IBM i “switch profile” CGI web service (httpd.conf ServerId), you would need to combine parts of XMLMAIN (direct call XMLSERVICE) and XMLCGI (excluding DB2 CLI).

XMLSERVICE Connections

Goto Main Page

Sometimes a real conversation helps …

Customer wants to know how to remain stateless, but get the benefit of being able to audit the use of RPG subprocedure invocations via XMLToolkit without toggling back and forth (different QSQSRVR jobs).

Stateless may use multiple QSQSRVR jobs (toggling), this is definition of ‘stateless’ in PHP fastcgi environment (working correct).

Will ToolkitService object using persistent db2 connections and “stateless => true” help single QSQ job?

Toolkit “stateless => true” via db2 transport using db2_connect or db2_pconnect makes no difference, fastcgi implies unpredictable QSQSRVR job will be used (command line slightly more predictable over web).

Why ‘unpredictable’ QSQ?

It’s all web PHP fastcgi ‘random’ worker selection, NOTHING to do with ibm_db2 ‘connection’ persistent or non-persistent, toolkit connection is irrelevant. Apache web site using fastcgi routes “randomly” to any available PHP worker job php-cgi, therefore ‘random’ php-cgi worker using db2_(p)connect<>QSQSRVR will also appear ‘random’. To wit, you end up different QSQSRVR jobs (toggle back and forth). Technically, fastcgi protocol, PHP workers poll a single fastcgi socket waiting to take some work, ZS on i at /www/zendsvr6/logs/fcgi-njjjineh.sock (sock name random). As each php worker strips work off fastcgi socket (1st come == 1st do), the worker is busy communicating on ‘private web socket’ running script until finished (no longer waiting). Combination of natural web worker selection (browser +/- KeepAlive), and fastcgi socket poll for work, results in ‘random’ appearance QSQ job toolkit usage, DB2 connection just came along for ride in back seat of the 1950 PHP fastcgi roadster.

Help me be one-and-only-one job?

The only way to assure private connection back to same XTOOLKIT job is ‘internalKey’=>’/tmp/XTOOLKIT_job_1’ (‘ipc’=/tmp/XTOOLKIT_job_1’).

XMLSERVICE connections discussed this page

  • Stateless – clean running come/go, “full open/close,” start/stop connection to be used by any requester. Must set LIBL during each request
  • State full – State retained between requests: LIBL, transactions, etc., “private” connection used by one requester/task for a long period of time (like 5250)
  • State full – hybrid “private/persistent” connection shared by many requesters, but keep open PGM, files, etc.
  • State full – reservation hybrid “private/persistent” connection exclusively held for a period each requesters, but returned back to pool for re-use (rare use)

The jobs involved in “connections”:

I. Stateless
                    (job 1)  (job 2)                (job 3)
                    Apache   FastCGI                DB2 (stateless)
                    -------  ---------------        ---------------------
  browser/client ->thread-->socket->php-cgi
                          --->$ctl="*here";      -->QSQSRVR+XMLSERVICE
                                                    shut down after
                                                    each request
                                                    ("stateless")

II. State full
                              (job 2)               (job 3)                 (job 4)
                              FastCGI               QSQ (proxy)             XMLSERVICE (state full)
                              ---------------       ---------------------   ------------------------
                          -->socket->php-cgi
                          --->$ctl="*sbmjob";
                          --->$ipc="/tmp/sally"; -->QSQSRVR+XMLSERVICE   -->XMLSERVICE
                                                                            alive until stopped
                                                                            ("state full")


Jobs originates::

  (job 1) Apache picks any thread (1st level routing)
  (job 2) FastCGI all "non-busy" worker php-cgi wait on unix domain socket /www/zend2/logs/fcgi-hmjadgek.sock (2nd level routing)
  (job 3) php-cgi - database connections odbc, ibm_db2, pdo_ibm (3rd level routing)
          db2_pconnect() attach to pooled/persistent QSQ (matching profile) but leaves connection open on exit
          db2_connect() acquires a unused pre-start QSQ (or starts one) then attaches to QSQ (profile), and returns to unused pool on exit
  (job 3) XMLSERVICE -- Stateless -- run inside QSQ job and clean-up after each request (3rd level routing)
          $ctl = "*here";
  (job 4) XMLSERVICE -- State full -- run in separate job that any QSQ job can call using IPC (4th level routing)
          $ctl = "*sbmjob";
          $ipc = "/tmp/sally";

Drivers involved in conection:

400 server start-up   Common usage    Big picture     Comments
STRTCPSVR SERVER(*HTTP)

    port 80 or 10088 or … (REST/HTTP interface)

  1-tier - XMLCGI     CLIENT <==> IBM HTTP Server <==> XMLCGI (CLI server mode) <==> QSQxxxx <==> IBM i Resources     XMLCGI + CLI
PASE library (no start needed)

    no port

  1-tier - PHP ibm_db2+pdo_ibm+odbc (Native PASE CLI libdb400.a driver)       CLIENT (PASE libdb400.a driver) <==> QSQxxx <==> IBM i Resources        Native PASE CLI libdb400.a driver (IBM Rochester)
STRHOSTSVR SERVER(*DATABASE)

    port 8471 (database)

  2-tier - PHP odbc interface (IBM i Client Access odbc driver interface)     CLIENT (Client Access drivers) <==> QZDAxxxx <==> IBM i Resources       Client Access odbc-based drivers (IBM Rochester)
STRTCPSVR SERVER(*DDM)

    port 446 (DDM/DRDA)

  2-tier - PHP ibm_db2+pdo_ibm (DB2 Connect driver interface)         CLIENT (DB2 Connect drivers) <==> QRWxxxx <==> IBM i Resources  DB2 CLI DRDA-based DB2 Connect drivers (IBM Toronto)

1) Stateless – no LIBL, come/go

These connections are traditional web requests “full open/close” clean running start/stop connection to be used by any requester.

  • Stateless
$ctl = "*here";

                 (1)           (2)           (3)
                 Apache        FastCGI       DB2 (server mode)
                 -------  ---------------    ---------------------
browser/client -->thread--socket->php-cgi--->QSQSRVR(profile fred)
                                             XMLSERVICE (fred)      <--shut down after each request
                                         --->QSQSRVR(profile sally)
                                             XMLSERVICE (sally)     <--shut down after each request
                                         --->QSQSRVR(profile john)
                                             XMLSERVICE (john)      <--shut down after each request

Example new Toolkit (stateless):

if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
try { $ToolkitServiceObj = ToolkitService::getInstance($conn); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->CLCommand("CHGLIBL LIBL(FREDFLIN WILMAFLIN) CURLIB(FREDFLIN)");

Stateless: If you choose $ctl=’*here’, you will run in the calling process DB2 connection (QSQSRVR job). When XMLSERVICE completes your XML script it will shut down to nothing, considered stateless and holds zero state on return.

  • In general you will run slower in stateless mode (CW default / Toolkit default), because XMLSERVICE has to keep starting things over and over and over again, but perhaps not an issue if you have CPU to burn.*
  • The is no semaphore locking or shared memory ipc when running as stateless (*here), because only one sally client/server is a pair, but of course there may be many sally client/server pairs on the same machine.*
  • There is no “memory” of the LIBL in stateless, so it must be set EVERY time before use.*

2) State full – LIBL, transactions, etc.

These connections are traditional 5250-like “private” connection used by one requester/task for a long period of time.

State full (most RPG programs)
$ctl = "*sbmjob";
$ipc = "/tmp/sally";
-- or --
$ipc = "/tmp/john";
                  (1)           (2)           (3)                       (4)
                  Apache        FastCGI       DB2 (server mode)         XMLSERVICE
                  -------  ---------------    ---------------------     ----------
                -->thread--socket->php-cgi--->QSQSRVR(profile sally)---.->XMLSERVICE (sally) <--alive until stopped (or idle timemout)
                                          --->QSQSRVR(profile john)--. |
                -->thread--socket->php-cgi--->QSQSRVR(profile fred)  | |
                                          --->QSQSRVR(profile sally)---.
                                          --->QSQSRVR(profile fred)  |
                                          --->QSQSRVR(profile john)--.--->XMLSERVICE (john) <--alive until stopped (or idle timemout)

Example new Toolkit (state full):

$internalKey = '/tmp/packers';
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
try { $ToolkitServiceObj = ToolkitService::getInstance($conn); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(array(
'InternalKey'=>$internalKey,  // *** RIGHT HERE internalKey/IPC
                              // *** run state full
                              //     use SBMJOB command run in new job
                              //     PHP can call again, again, again
                              //     with /tmp/packers and get
                              //     same job every time
                              //     same library list (*LIBL)
                              //     same PGMs with open files, etc.
                              //     exactly like 5250 sign-on screen
'plug'=>"iPLUG32K"));         // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
// state full - MUST do this ONCE ONLY after start/sbmjob of XMLSERVICE job
//              then forget about it (unless you choose to change libl)
$ToolkitServiceObj->CLCommand("CHGLIBL LIBL(FREDFLIN WILMAFLIN) CURLIB(FREDFLIN)");
/* Do not use the disconnect() function for "state full" connection */
/* NEVER EVER USE THIS ... $ToolkitServiceObj->disconnect();        */
/* Why? *immed kill of job, not nice or sync, just kill             */
/* Use idle timeout for "state full" / "private" connections        */

State full: If you choose $ctl="\*sbmjob" + $ipc="/tmp/packers", you will run in a separate job past the calling DB2 connection (child job of QSQSRVR). This $ctl/$ipc combination will allow you to return to the same XMLSERVICE job from any connection to the machine, therefore considered “state full” and any called program can keep open files, transactions, etc. (just like a real RPG 5250 program does mate).

  • $ipc=’/tmp/anything’ can be any unique/accessible directory you want to route you back to same XMLSERVICE job (*sbmjob), but usually anchored in /tmp directory because xmlservice will try to create it if missing.
  • Technically $ipc=”/tmp/packers” is a unique IFS machine location in posix function ftok(‘/tmp/packers’) which presents a unique numeric key representing /tmp/packers that is used for XMLSERVICE shared memory and semaphores creation/attach (XMLSERVICE uses shared memory/semaphores for communication).
  • Shared memory + semaphore locking is only required for state full connections ($ctl=”*sbmjob” + $ipc=”/tmp/packers”), where each sally XMLSERVICE semaphore “front door lock” will allow only one sally client to chat with a XMLSERVICE job, the other sally requesters will wait until they are invited to chat (just like the dentist office).
  • Security is managed through IFS shared memory / semaphores access control just like any other IFS file, so once profile sally owns an active XMLSERVICE ctl+ipc then no other profile can attach to the active XMLSERVICE job … well … except for high authority profiles like *SECOFR (of course).
  • With version 1.6.6 state full XMLSERVICE connections are ended via configurable idle timeout $ctl .= ” *idle(3000)”, you may keep the jobs alive forever using $ctl .= ” *idle(0)” to match the original version behavior. There are other options for client wait $clt .= ” *wait(30)” and waiting for called program to return $ctl .= ” *call(200)” and various actions that can be taken for each wait/timer (busy,kill,etc.).
  • In this example we have been using one sally client/server $ipc=”/tmp/packers”, you can of course have many different sally client/server ($ipc=”/tmp/packers”, $ipc=”/tmp/vikings”, $ipc=”/tmp/bears”, etc.) and each of these sally ipcs may have many clients chatting with each sally server ipc … sort of a sally work load balancing dream situation where we can clone a new sally ipc server for each task at hand.
  • You only need to set the LIBL once in state full (unless you want to change LIBL for some reason).

Toolkit State full (with IPC) - avoid start up/shut down XMLSERVICE (NOT toolkit default)

  • avoid using toolkit disconnect ($ctl=”*immed”) to leave XMLSERVICE up and running (will timeout shut down if idle for 1/2 hour)
  • choose a the minimum plug size need for the task to avoid send/receive extra blanks
  • TURN DEBUG and LOGS off in toolkit to avoid IFS file write (takes forever in computer timings)
  • db2_pconnect() - persistent or “shared” connection with toolkit avoids acquire/release QSQ jobs (NOT toolkit default)
XMLSERVICE adopt authority issues

When using ctl+ipc “state full” jobs is a generally bad idea to “adopt authority” as originating profile sally will lose all access … and … in fact ipc may become unreachable causing a orphan XMLSERVICE (client is still sally, ipc is still sally’s, but adopt xmlservice server becomes fred).

Two choices:

  1. If you MUST “adopt authority” do it in a stateless job (*here), where full connection processing may undo “left over switch profile” potential damage on the way out of XMLSERVICE script. This option should always work.
  2. Be very careful to return back to sally profile EACH TIME leaving xmlservice sending data back to the waiting sally client … sort of good manners talk to sally client as sally server (adopted fred can speak/do only when asked, then go away)

Note: We are thinking about forcing “switch back to originating profile on the way back” within XMLSERVICE code, but have not yet understood what that means to PHP wrappers like CW, so the mission is at the moment in your called program and/or PHP wrapper/user code.

3) State full – hybrid “private/persistent” connection

These connections are hybrid “private/persistent” connection shared by many requesters, but keep open PGM, files, etc.

Worried about too many IPC’s/XMLSERVICE jobs??

The following gives you a hybrid “private/persistent” connection

  • most all the benefits for called RPG (state full, open files, etc.)
  • but only $maxpool XMLSERVICE jobs

Try this simple technique for pooled IPC’s/XMLSERVICE jobs $internalKey = '/tmp/packers'.rand(1,$maxpool)

  • IF your application set can tolerate multi-client shared access to a pool of persistent/private/semi-stateless connections the random technique should work well.
  • However, if you need your client make a exclusive reservation see the next topic

State full – hybrid “private/persistent” connection

$maxpool = 3;
// -- PHP raw ---
$ctl     = "*sbmjob";
$ipc     = "/tmp/sally".rand(1,$maxpool);
// -- or PHP toolkit --
$internalKey = '/tmp/sally'.rand(1,$maxpool)

                 (1)           (2)           (3)                       (4)
                 Apache        FastCGI       DB2 (server mode)         XMLSERVICE
                 -------  ---------------    ---------------------     ----------
               -->thread--socket->php-cgi--->QSQSRVR(profile sally)--.
               -->thread--socket->php-cgi--->QSQSRVR(profile sally)--|
                   :                                                 |
               -->thread--socket->php-cgi--->QSQSRVR(profile sally)--|->XMLSERVICE (/tmp/sally1) <--alive until stopped (or idle timemout)
               -->thread--socket->php-cgi--->QSQSRVR(profile sally)--|->XMLSERVICE (/tmp/sally2) <--alive until stopped (or idle timemout)
               -->thread--socket->php-cgi--->QSQSRVR(profile sally)--|->XMLSERVICE (/tmp/sally3) <--alive until stopped (or idle timemout)
                   :                                                 |
               -->thread--socket->php-cgi--->QSQSRVR(profile sally)--|
               -->thread--socket->php-cgi--->QSQSRVR(profile sally)--.

3 XMSLERVICE jobs handle work for all sally clients using the site

Example new Toolkit (hybrid “private/persistent” connection):

$maxpool = 40; // 40 jobs good enough to handle my machine needs

if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);

try { $ToolkitServiceObj = ToolkitService::getInstance($conn); }
catch (Exception $e) { die($e->getMessage()); }

$internalKey = '/tmp/packers'.rand(1,$maxpool);
$ToolkitServiceObj->setToolkitServiceParams(array(
'InternalKey'=>$internalKey,  // *** RIGHT HERE internalKey/IPC $maxpool jobs for service
'plug'=>"iPLUG32K"));         // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)

/* Do not use the disconnect() function for "state full" connection */
/* NEVER EVER USE THIS ... $ToolkitServiceObj->disconnect();        */
/* Why? *immed kill of job, not nice or sync, just kill             */
/* Use idle timeout for "state full" / "private" connections        */
  • So simple, why does it work???

    • Works much same as Apache FastCGI PHP jobs (even using random), because $maxpool “child XMLSERVICE workers” can be increased to match machine workload (tinker-trial-error) … just like Apache threads … just like PHP children … all the same
    • Most web requests are sub-second, so even on routing collision by random it is a short wait.
  • Could i dedicate different pools to different tasks ???

    • Yes, a bag full of really low effort work (/tmp/packers1-40, /tmp/vikings1-40).
  • Could i dedicate different user ids to different pools as well as tasks???

    • Yes, a bag full of really low effort work (/tmp/packers1-40, /tmp/vikings1-40).
  • Can i idle timeout unused XMLSERVICE jobs ??? + Yes of course, toolkit.ini setting or specify manually. + NEVER EVER USE THIS … $ToolkitServiceObj->disconnect();

  • Should i use persistent connections??

    • db2_pconnect – Yes of course, it will save the time “attaching” a QSQSRVR job
    • db2_connect – However, you can use same technique with full open/close (yes it does work, try it)
  • Can i prestart jobs?

    • Yes, but they will start on web demand which i think is much better (just like Apache)
    SBMJOB CMD(CALL PGM(ZENDSVR/XMLSERVICE) PARM('/tmp/packers1')) JOBD(ZENDSVR/ZSVR_JOBD) USER(PACKERS)
    SBMJOB CMD(CALL PGM(ZENDSVR/XMLSERVICE) PARM('/tmp/vikings1')) JOBD(ZENDSVR/ZSVR_JOBD) USER(VIKINGS)
    

4) State full – reservation hybrid “private/persistent” connection

These connections are hybrid “private/persistent” connection exclusively held for a period of time by each requesters, but returned back to pool for re-use.

If your client needs to start/use/stop a reservation hybrid “private/persistent” connection, use the appropriate keyword in your XML sent to XMLSERVICE to gain exclusive rights to the hybrid “private/persistent” connection.

  • <start>unique-user-key</start> – acquire exclusive IPC if available

  • <use>unique-user-key</use> – must appear XML every request job held forever until see <stop>

  • <stop>unique-user-key</stop> – release IPC for any other use

  • Errors:

    • <use>no-match-user-key</use> – non-matching key results in error almost instantly (no wait)

      busy response (1301060):
        <error>
          <errnoxml>1301060</errnoxml>
          <xmlerrmsg>IPC owner busy</xmlerrmsg>
        </error>
      
    • thoughtful setting server idle timeout can control unwanted reservation hangs due to careless users or errors ** $ctl .= ” *idle(60)” **

hybrid “private/persistent” connection with reservation

$maxpool = 3;
// -- PHP raw ---
$ctl     = "*sbmjob";
$ipc     = "/tmp/sally".rand(1,$maxpool);
// -- or PHP toolkit (not available yet -- Alan) --

                 (1)           (2)           (3)                       (4)
                 Apache        FastCGI       DB2 (server mode)         XMLSERVICE
                 -------  ---------------    ---------------------     ----------
               -->thread--socket->php-cgi--->QSQSRVR(profile sally)--.
               -->thread--socket->php-cgi--->QSQSRVR(profile sally)--|
                   :                                                 |
               -->thread--socket->php-cgi--->QSQSRVR(profile sally)--|->XMLSERVICE (/tmp/sally1)       <--alive until stopped (or idle timemout)
                                                                        <start>unique-user-key</start> <--exclusive reservation until stopped
                                                                        <use>unique-user-key</use>
                                                                        <stop>unique-user-key</stop>
               -->thread--socket->php-cgi--->QSQSRVR(profile sally)--|->XMLSERVICE (/tmp/sally2) <--alive until stopped (or idle timemout)
                                                                        <start>unique-user-key</start> <--exclusive reservation until stopped
                                                                        <use>unique-user-key</use>
                                                                        <stop>unique-user-key</stop>
               -->thread--socket->php-cgi--->QSQSRVR(profile sally)--|->XMLSERVICE (/tmp/sally3) <--alive until stopped (or idle timemout)
                                                                        <start>unique-user-key</start> <--exclusive reservation until stopped
                                                                        <use>unique-user-key</use>
                                                                        <stop>unique-user-key</stop>
                   :                                                 |
               -->thread--socket->php-cgi--->QSQSRVR(profile sally)--|
               -->thread--socket->php-cgi--->QSQSRVR(profile sally)--.

3 XMSLERVICE jobs handle work for all sally clients using the site However, reservation locks exclusive use until reservation is stopped.

Example new Toolkit (hybrid “private/persistent” connection with reservation):

--- unfortunately reservation is not available in PHP wrapper yet (Alan) ---
--- raw xml pseudo code version of what happens follows start/use/stop   ---
    -- no time out --
    $ctl .= " *idle(0)"
    -- request 1 --
    <?xml version="1.0"?>
    <script>
    <start>unique-user-key</start>
    </script>
    -- request 2 (two minutes later) --
    <?xml version="1.0"?>
    <script>
    <use>unique-user-key</use>
    <cmd exec='rexx'>RTVJOBA USRLIBL(?)</cmd>
    </script>
    -- request 3 (1/2 hour later) --
    <?xml version="1.0"?>
    <script>
    <use>unique-user-key</use>
    <pgm name='ZZCALL'>
      <parm>
      <data type='1A'>a</data>
      </parm>
      <return>
        <data type='10i0'>0</data>
      </return>
    </pgm>
    </script>
    -- request n (2 hours later) --
    <?xml version="1.0"?>
    <script>
    <stop>unique-user-key</stop>
    </script>

XMLSERVICE IPC

Goto Main Page

Toolkit internalKey, IPC, toolkit persistent connection …

With exception of old CW layer, DB2 connection persistent/full makes NO difference, internalKey (IPC) applies ONLY to XMLSERVICE layer to route back to same xmlservice job every time. This routing technique is referred to as a “state full” XMLSERVICE connection.

The naming of the internalKey (IPC) varies across layers PHP Toolkit, but it means the same thing.

  • New PHP Toolkit - refers to internalKey (‘InternalKey’=>”/tmp/packers”)
    • Note: works with both persistent and non-persistent connections (db2_pconnect or db2_connect, odbc_pconnect or odbc_connect)
  • XMLSERVICE RAW - refers to IPC ($ipc=”/tmp/packers”)
    • Note: works with both persistent and non-persistent connections (db2_pconnect or db2_connect, odbc_pconnect or odbc_connect)
  • New PHP Toolkit CW Layer - refers to “private connection” and provides APIs to get/set private “key/nbr”
    • Note: CW Layer requires “persistent connection” to achieve “private connection”, this old technology is just for compatibly with old toolkit concepts

How IPC/internalKey works?

The internalKey (IPC) provided by the user (or PHP wrapper) is simply a unique place in the IFS file system. That is to say that one and only one /tmp/packers lives on the machine (LPAR IBM i instance), therefore it is very handy to hash this location into a key (google ftok), that can be used to create unique purpose semaphores (locks) and shared memory (shared data) on the IBM i.

IFS /tmp/path only provides a unique key (hash key), therefore all manner of IPC-2-xmlservice workload/user balancing could be imagined, in fact you could restrict your site to only the IFS paths pre-created in some lower level directory to avoid any unwanted user ability to start an xmlservice job assuming toolkit wrapper plays along beyond using just /tmp.

When an active xmlservice session is running on /tmp/packers you can see the semaphores and shared memory using the utility ipcs. Please note authorizations ipcs displays are exactly like any other IFS file, including owner access (RW, read/write) and *PUBLIC access (–, none), etc. This is how xmlservice controls authorization front door to a state full XMLSERVICE job allowing only correct/matching profiles to call any specific xmlservice service job (one request at a time).

call qp2term
> ipcs
   SHARED MEMORY:
   T        ID     KEY       MODE          OWNER      GROUP
   M      2306 0X010404F7 T-RW-------        DB2      *NONE   <---- /tmp/packers (shared data)
   SEMAPHORES:
   T        ID     KEY       MODE          OWNER      GROUP
   S       377 0X010404F7 --RW-------        DB2      *NONE   <---- /tmp/packers (lock one use at time)

PHP program decode hex IPC key

Unfortunately ipcs “KEY” column is displayed in hex, so if you want to see what goes with /tmp/packers you will need to run a little program using $ctl="*session".

zzftok2.php:
<?php
require_once('connection.inc');
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$ctl = "*session";
$ipc = "/tmp/packers";
$clobIn = "<?xml version='1.0'?>";
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
var_dump($clobOut);
?>

> http://myibmi/zzftok2.php
string(70) "<?xml version='1.0'?>
<session key='010404F7'>/tmp/packers</session>

XMLSERVICE/Toolkit idle time out

Goto Main Page

Who is this page for?

Instructions designed for IBM i developer learning PHP and XMLSERVICE …

How to make your toolkit jobs time out

XMLSERVICE from 1.6.2 onward contains flexible timeout features, including the ability to have a program (RPG, COBOL, CL, whatever) time out if it runs too long. This is useful to end jobs that get hung with a MSGWAIT condition.

The PHP toolkit wrappers currently enable only one of these timeout features: the “idle timeout.” When using a private connection job, also known as jobs that have an IPC or InternalKey, the job can be made to time out when a certain number of seconds of inactivity have elapsed.

Cleanliness vs. performance

Causing jobs to time out quickly will give you a nice, empty, clean-looking ZENDSVR subsystem, but will drag down performance the next time the job is started up. Try to find a balance between cleanliness and performance. If you plan to use the same jobs over and over, you may wish to NOT time out the jobs, either never ending them or ending them *IMMED at night, or some other scheme.

How to choose a timeout value If you have many transient users who will briefly use the site and then not return, you may want a quick timeout (30 seconds?). For users who will return over and over again, you may want a long or nonexistent timeout.

  • Idle timeout with the new toolkit API

The “idle timeout” can be set or changed any time in this way:

// let's assume the toolkit connection has been established using getInstance() and is present in the variable $conn:
$idleTimeoutSeconds = 1800; // time out job after 1800 seconds (30 minutes) of inactivity
                        // a value of 0 means no timeout (infinite wait)
$conn->setOptions(array('idleTimeout' => $idleTimeoutSeconds));
  • Idle timeout with the Compatibility Wrapper (CW)

Just as with the old toolkit, when you connect with i5_pconnect(), you can set the timeout interval and the number of seconds to wait. As with the old toolkit, idle timeouts only work with private connections. Use the constant I5_OPTIONS_IDLE_TIMEOUT to provide the number of seconds. Use zero (0) seconds to never time out (the default).

$privateNum = 0; // private conn number of zero causes the CW to generate a number for next time.
$idleTimeoutSeconds = 1800; // time out job after 1800 seconds (30 minutes) of inactivity
                        // a value of 0 means no timeout (infinite wait)
      $options = array(I5_OPTIONS_PRIVATE_CONNECTION => $privateNum,
                  I5_OPTIONS_IDLE_TIMEOUT       => $idleTimeoutSeconds
                  );

      // connect (note: the "p" for persistent is required for CW private connections)
      // and specify a private connection and timeout
      $conn = i5_pconnect('localhost', 'user', 'pw', $options);

Note: The CW also supports the new API’s “setToolkitServiceParams” technique described above, because the CW uses the new toolkit underneath.

Full support RAW XMLSERVICE Samples

Programs with qsysopr message idle timeout may help.

<?php
// see connection.inc param details ...
require_once('connection.inc');
// call IBM i
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG32K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$ctl .= " *idle(10)";
$clobIn = getxml();
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
// -----------------
// output processing
// -----------------
// dump raw XML (easy test debug)
var_dump($clobOut);
// xml check via simplexml vs. expected results
$xmlobj = simplexml_load_string($clobOut);
if (!$xmlobj) die("Bad XML returned");
// good
if (strpos($clobOut,"Pointer not set")<=0) echo "Failure\n";
echo "Success\n";

//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//      * zzboom: bad function blow up
//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//     P zzboom          B                   export
//     D zzboom          PI
function getxml() {
$clob = <<<ENDPROC
<?xml version='1.0'?>
<script>
<pgm name='ZZSRV' lib='xyzlibxmlservicexyz' func='ZZBOOM'>
</pgm>
</script>
ENDPROC;
return test_lib_replace($clob);
}
?>

Timeout is a setting control setting with various properties allowed.

*----------------------------------------------------
* -- server time out jobs XMLSERVICE (1.6.2)
* *wait[(seconds[/action])]
*    - client side wait for XMLSERVICE call (client side)
*      example: $ctl="*wait(10)";
*      - default action *wait(60/busy) (see plugconfx)
* *call[(seconds[/action[/duration[/job]]])]
*    - client/server side XMLSERVICE call wait (PGM, SRVPGM, PASE, etc)
*      example: $ctl="*wait(10) *call(5/busy/client)";
*      - default for both client/server is *call(0)
*        means wait on call forever (user code flawless),
*        but can be overriden client/server/both
* *idle[(seconds[/action[/duration]])]
*    - server side XMLSERVICE idle no activity
*      example: $ctl="*wait(10/kill) *call(30/kill) *idle(30/perm)";
*      - default action *idle(1800/kill) (see plugconfx)
*    -- time out parameters
*      seconds:
*        -1 - current default timer
*         0 - no timer, no timeout, wait forever
*         n - idle timer "pop" seconds
*      action:
*        kill - end job immed
*        user - user override signal behaviour (see plugconfx)
*        busy - return busy XML (client side)
*               busy response (1301050):
*               <error>
*               <errnoxml>1301050</errnoxml>
*               <xmlerrmsg>IPC timeout busy</xmlerrmsg>
*               </error>
*      duration:
*        perm - set and use new defaults all requests
*        orig - reset and use original compile defaults (see plugconfx)
*      job:
*        client - *call action applies client side
*        server - *call action applies server side
*    -- Notes:
*      - default timeout/action provided plugconf.rpgle,
*        but each request may override/reset to fit task(s)
*      - signal SIGALRM used with this function
*        can affect user program calls,
*        *call(0) may be used to turn off timer
*        during user program calls
*      - action 'user' allows for custom signal
*        processing in the RPG code (see plugconfx)
*      - if duration not specified, attributes
*        *wait(),*call(),*idle() are temporary
*        for this call only and return to last defaults.
*      - if 'job' not specified on *call(),
*        attribute settings apply to both sides
*      - end job immed kills XMLSERVICE job (server)
*        and destroys IPC, so any waiting client is
*        released with an IPC missing error.
*----------------------------------------------------

XMLSERVICE/Toolkit debugging and service

Goto Main Page

Who is this page for?

Instructions designed for IBM i developer learning PHP and XMLSERVICE …

Debug technique: It’s as easy as 1-2-3-4-5-6-7-8-9 :)

  1. Run the test script that contains control “*debug” and script will “hang” while it waits on #2

$ctl .= " *debug";

  1. A MSGW inquiry message in DSPMSG QSYSOPR will be generated by the toolkit. Note the job information (number, name, user) provided in the MSGW.
  2. STRSRVJOB using that job information as parameters.
  3. STRDBG with the program and library you wish to debug.
  4. Answer the MSGW. Any answer will do–“G” is fine.
  5. The RPG program source will appear in debug mode in your terminal, ready to step through, allowing you to inspect variables, etc.
  6. When done inspecting and stepping, let the RPG program complete (using function keys indicated on screen).
  7. ENDDBG
  1. ENDSRVJOB

Other debug options:

          Job1 (threaded)   Job 2                        Job 3 (DB2 userid/password)    Job 4 (optional XTOOLKIT job)
                            (ctl=*debugcgi)              (ctl=*debugproc)                (ctl=*debug)
browser -> Apache          ->XMLCGI (Apache CGI child) -> QSQSRVR (XMLSERVICE *here)
                                                      -> QSQSRVR (XMLSERVICE client) -> XTOOLKIT (XMLSERVICE ipc=/tmp/flinstone)

$ctl .= " *debugcgi";  // Job 2 - debug XMLCGI to see REST/HTTP data passed by client (when using REST only)
$ctl .= " *debugproc"; // Job 3 - debug XMLSERVICE "client" to see DB2 passed data (DB2 interface)
$ctl .= " *debug";     // Job 4 - debug XMLSERVICE "server" to see XMLSERVICE calls (DB2 interface)
                      // Note:   when ctl='*here', both XMLSERVICE "client"/"server"
                      //         are in QSQSRVSR job (NO XTOOLKIT job)
                      // remote: Attaching with LUW drivers changes QSQSRVR ...
                      //   CLIENT (Client Access drivers) <==> QZDAxxxx
                      //   CLIENT (DB2 Connect drivers)   <==> QRWxxxx

Working with service provider?

Here are a few common things that can provide useful information if working with outside support people.

  1. easy way 1.7.1 … just send provider XMLSERVLOG/LOG and XMLSERVLOG/DUMP in SAVF

assuming you have a trace of the issue (see xmlservice logging instructions)

  1. what do you see in error logs for simple tests ???
call qp2term
> tail /usr/local/zendsvr/var/log/php.log
> tail /myasp2/www/zend2/logs/error_log.Q112061800
> tail /myasp2/www/zend2/logs/access_log.Q112061800
  1. Summarize configuration (below)???

My Example

SYSASP -- ZENDSVR library ... everything as installed
SYSASP -- /usr/local/zendsvr ... everything as installed
SYSASP -- /tmp ... many Zend "enterprise" components use /tmp
MYASP2 -- /myasp2/www/zend2 ... ALL "user data" moved from /www/zendsvr (/conf, /logs, /htdocs)
More ... /myasp2/www/zend2
-- NO symbolic links between SYSAPS and MYASP2 for true "independent"ASP.
-- config files /myasp2/www/zend2/conf
attach: fastcgi.conf
attach: httpd.conf
  1. Around time of simple test failure … do you see job logs?

wrkoutq

If jobs are still active (php-cgi, etc.) ...
wrkactjob
5      ZEND2        QTMHHTTP    BCI      .0  PGM-QZSRHTTP     SIGW
        ZEND2        QTMHHTTP    BCI      .0  PGM-php-cgi.bi   THDW
5      ZEND2        QTMHHTTP    BCI      .0  PGM-php-cgi.bi   TIMW
10. Display job log, if active, on job queue, or pending
F10 -- full job log
  1. Around time of simple test failure … do you see VLOGS???
STRSST
1. Start a service tool
5. Licensed Internal Code log
1. Select entries from the Licensed Internal Code (LIC) log
Specify Licensed Internal Code Log Selection Values
-- leave as is enter ---
      0100A890  i5/OS PASE               4700  0013  06/15/12  09:03:43      7        <--- PASE
      0100A891  LIC log interface        0401  0100  06/15/12  09:04:08      1
      0100A892  Signals management       4600  0001  06/15/12  09:04:51    255
      0100A893  Process management       1300  0001  06/18/12  21:02:06      1

Maybe look for PASE, storage management, ASP, so on around “failure time”

Note: You can also dump logs to spool …

When you have no idea (dumping many processes)?

Some times you just have no idea what is going on, here is a handy macro to dump a lot of stacks.

STRSST/STRDST
1. Start a service tool
4. Display/Alter/Dump
1. Display/Alter storage
... or option for dump to printer ...
2. Licensed Internal Code (LIC) data
14. Advanced analysis
Option    Command
    1      processinfo

In this case dumping all process dealing with keyword "ZEND" appearing in job ...

                      Specify Advanced Analysis Options
Output device  . . . . . . :   Display
Type options, press Enter.
  Command . . . . :   PROCESSINFO
  Options . . . . .   -NAMES ZEND

Note: Information dumped printer/display is same as paseps macro.

Check active XMLSERVICE job

If you are using private connections (InternalKey or $ipc=’/tmp/packers’), the XMLSERVICE job is probably available for examination with wrkactjob.

                            Work with Active Jobs                     LP0264D
                                                            05/17/12  11:35:12
CPU %:      .0     Elapsed time:   00:00:00     Active jobs:   313

Type options, press Enter.
  2=Change   3=Hold   4=End   5=Work with   6=Release   7=Display message
  8=Work with spooled files   13=Disconnect ...
                    Current
Opt  Subsystem/Job  User        Type  CPU %  Function        Status
5      XTOOLKIT     DB2         BCH      .0  PGM-XMLSERVICE   SEMW
  1. Use option 5=work -> 10. Display job log -> F10=Display detailed messages to examine joblog on errors …
                              Display All Messages
                                                            System:   LP0264D
Job . . :   XTOOLKIT      User . . :   DB2           Number . . . :   435915

  >> CALL PGM(XMLSERVICE/XMLSERVICE) PARM('/tmp/packers')
    Pointer not set for location referenced.
    Application error.  MCH3601 unmonitored by ZZSRV at statement 0000000448,
      instruction X'0000'.
  1. Use option 5=work -> 11. Display call stack -> F5=Refresh to examine stack during stress tests …
                              Display Call Stack
                                                            System:   LP0264D
Job:   XTOOLKIT       User:   DB2            Number:   437582
Thread:   0000000C


Type  Program                  Statement         Procedure
    1  QCMD       QSYS                     /01C8
      XMLSERVICE XMLSERVICE                      _QRNP_PEP_XMLSERVICE
      XMLSERVICE XMLSERVICE    1133              XMLSERVICE
      XMLSERVICE XMLSERVICE    4607              RUNSERVER
      XMLSERVICE XMLSERVICE    2983              SIGSETTIMEOUT
      XMLSERVICE XMLSERVICE    2876              SIGTIMEROFF
      QP0SSRV1   QSYS          19                setitimer
      QP0SSRV2   QSYS          159               qp0sitimer__F12qp0sitimer_t >

Check the logs

Check PHP log for messages

On my IBM i machine:

EDTF STMF('/usr/local/zendsvr/var/log/php.log')
-- or --
call qp2term (or ssh myibmi)
> tail /usr/local/zendsvr/var/log/php.log
... stuff
... in /MYASP2/www/zend2/htdocs/hello.php on line 1

On my Linux machine:

$ tail /usr/local/zend/var/log/php.log
[16-May-2012 16:30:12] PHP Warning:  db2_close() expects parameter 1 to be resource ...

Check Apache logs for messages

error logs for date in question:

EDTF STMF('/myasp2/www/zend2/logs/error_log.Q112051500')
-- or --
call qp2term (or ssh myibmi)
> tail /myasp2/www/zend2/logs/error_log.Q112051500
[Tue May 15 17:10:11 2012] [error] [client 9.5.158.38] CGI PROGRAM /QSYS.LIB/XMLSERVICE.LIB/XMLCGI.PGM RETURNED EXCEPTION ID CEE9901
[Tue May 15 17:10:11 2012] [error] [client 9.5.158.38] SEE JOBLOG FOR JOB 428979/QTMHHTTP  /ZEND2

access logs for date in question:

EDTF STMF('/myasp2/www/zend2/logs/access_log.Q112051500')
-- or --
call qp2term (or ssh myibmi)
> tail /myasp2/www/zend2/logs/access_log.Q112051500
9.5.158.38 - - [15/May/2012:17:47:41 -0500] "GET /cgi-bin/xmlcgi.pgm?db2=LP0264D

Check PHP Toolkit logs for messages

toolkit.ini logfile

EDTF STMF('/usr/local/zendsvr/share/ToolkitApi/toolkit.log')
-- or --
call qp2term (or ssh myibmi)
> tail /usr/local/zendsvr/share/ToolkitAPI/toolkit.log
15 May 2012 22:53:35.752099 Running stateless; no IPC needed. Service library: ZENDSVR
15 May 2012 22:53:36.588466 i5Error: num=14 cat=9 msg="No more entries." desc="No more entries."

location set in toolkit.ini ...
EDTF STMF('/usr/local/zendsvr/share/ToolkitApi/toolkit.ini')
[log]
; warnings and errors will be written to the logfile.
logfile = "/usr/local/zendsvr/share/ToolkitApi/toolkit.log"

toolkit.ini debugLogFile

EDTF STMF('/usr/local/zendsvr/share/ToolkitApi/debug.log')
-- or --
call qp2term (or ssh myibmi)
> tail /usr/local/zendsvr/share/ToolkitAPI/debug.log
<data type='1A' var='ds1' comment='DSCHARA'><![CDATA[E]]></data>
<data type='1A' var='ds2' comment='DSCHARB'><![CDATA[F]]></data>

location set in toolkit.ini ...
EDTF STMF('/usr/local/zendsvr/share/ToolkitApi/toolkit.ini')
; debug turns PHP toolkit's debug mode on or off (true/false). Default log file: /usr/local/zendsvr/share/ToolkitApi/debug.log
; This log will grow large, so leave this false when you do not need to log everything.
debug = true
debugLogFile = "/usr/local/zendsvr/share/ToolkitApi/debug.log"

PHP and XMLSERVICE bad XML

EDTF STMF('/tmp/bad.xml')
-- or --
> tail /tmp/bad.xml
start
<?xml version="1.0" encoding="ISO-8859-1" ?><script><cmd><success><&#1836;CDATA&#65533;+++ success QSYS/DLTDTAARA DTAARA(XMLSERVICE/BETTYBOOP)||></success></cmd>

Check one level at a time

Troubles on your PHP site or installation???

Often times if you take a deep breath, slow down and look at each level of web site components you can find your issue without reaching for the bat phone and calling Zend or IBM. The following set of tests walks up the PHP levels of components to give you confidence you are looking at the correct layer of your issue. Of course after you complete smaller PHP scripts (hello.php, etc.), you can likely use the same step up next level techniques on your sophisticated applications (WorldPeace.php).

Level 0 – PHP Working

If you are running on the IBM i machine it is always best to make sure your Apache/PHP setup can do anything.

Note: Your machine may have document root at /www/zendsvr vs. /MYASP2/www/zend2 (out-of-box installed Zend Server).

Test number #0

Install the following simple test in your Document root and see if “Hello World” appears.

/MYASP2/www/zend2/htdocs/hello.php
<?php
echo "Hello world";
?>

Run the test:

call qp2term (or ssh myibmi)
> export PATH=/usr/local/zendsvr/bin:$PATH
> export LIBPATH=/usr/local/zendsvr/lib
> cd /MYASP2/www/zend2/htdocs
> php hello.php
Hello world>

Level 1 – Apache/PHP working

If you are running on the IBM i machine it is always best to make sure your Apache/PHP setup can do anything.

Note: Your machine may have document root at /www/zendsvr vs. /MYASP2/www/zend2 (out-of-box installed Zend Server).

Test number #1

Install the following simple test in your Document root and see if “Hello World” appears in your browser.

/MYASP2/www/zend2/htdocs/hello.php
<?php
echo "Hello world";
?>

Ok, next run stress test …

call qp2term
> cd /usr/local/Zend/apache2/bin
> ab -t 25 -c 10 http://myibmi/hello.php
-t 25 -- 25 seconds
-c 10 -- 10 concurrent browsers (simulates ten browsers)
  • this test is designed to drive CPU to 100% (a good thing), so don’t panic about CPU

wrkactjob refresh (F10) - if you do not see multiple php-cgi jobs getting CPU re-run tests with more load

call qp2term
> cd /usr/local/Zend/apache2/bin
> ab -t 25 -c 10 http://myibmi/hello.php &; ab -t 25 -c 10 http://myibmi/hello.php &; ab -t 25 -c 10 http://myibmi/hello.php &;
-t 25 -- 25 seconds
-c 10 -- 10 concurrent browsers (simulates ten browsers)
Multiply by 3 Apache ab jobs running background -- 30 concurrent browsers (simulates thirty browsers)
  • above using “&;” to fork/exec into background/batch many jobs(s) Apache ab to really hammer your web site
  • ab tool is not perfect, so if you start too many of these forked jobs (’&;’) the tool may core dump (die) – i usually can get 3 - 6 jobs working (30-60 browsers)
  • if you still do not see a split of CPU between php-cgi jobs you may be missing HTTP PTFS

Level 2 – db2 connection working

At this level we want to check our DB2 connections.

Note:

  • Your machine may have document root at /www/zendsvr vs. /MYASP2/www/zend2 (out-of-box installed Zend Server).
  • This same test can be run 2-tier from Linux/Windows to IBM i using DB2 Connect

Test number #2

Install the following simple test in your Document root and see if “success” appears in your browser.

/MYASP2/www/zend2/htdocs/connection2.inc
<?php
$database            = "*LOCAL"; // *LOCAL on IBM i ... LP0264D on Linux
$cwdatabase          = "localhost";
$user                = "DB2";
$password            = "XXXXXXXX";
$libxmlservice       = "ZENDSVR"; // ZZCALL (Zend Server)
$i5persistentconnect = false;
?>

/MYASP2/www/zend2/htdocs/xxtoolkit_connect.php
<?php
require_once('connection2.inc');
// flip between persistent and non-persistent connections
for ($i=0;$i<500;$i++) {
  for ($i5persistentconnect=1;$i5persistentconnect>-1;$i5persistentconnect--) {
    if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
    else $conn = db2_connect($database,$user,$password);
    if (!$conn) echo "<br>Bad connect: $conn,$database,$user,perm=$i5persistentconnect";
    else echo "<br>Good connect:  $conn,$database,$user,perm=$i5persistentconnect";
    if ($i5persistentconnect) $ok = true;
    else $ok = db2_close($conn);
    echo ",ok=$ok\n";
  }
}

run the test Apache …

Point your browser to php program ...
http://myibmi/xxtoolkit_connect.php
Good connect: Resource id #2,*LOCAL,DB2,perm=1,ok=1
Good connect: Resource id #3,*LOCAL,DB2,perm=0,ok=1
Good connect: Resource id #4,*LOCAL,DB2,perm=1,ok=1
:

run the test 2-tier … I choose to run from command line on my Linux machine, but Linux Apache would also work.

$ which php
/usr/local/zend/bin/php
$ php xxtoolkit_connect.php
<br>Good connect:  Resource id #5,LP0264D,DB2,perm=1,ok=1
<br>Good connect:  Resource id #6,LP0264D,DB2,perm=0,ok=1
<br>Good connect:  Resource id #7,LP0264D,DB2,perm=1,ok=1
:

Level 3 - XMLSERVICE XML interface Working

At this level we want to check our PHP raw XML Toolkit built on top of ibm_db2 connections (Level 2).

Note:

  • Your machine may have document root at /www/zendsvr vs. /MYASP2/www/zend2 (out-of-box installed Zend Server).
  • This same test can be run 2-tier from Linux/Windows to IBM i using DB2 Connect.
  • RPG test *PGM ZENDSVR/ZZCALL is included with Zend Server installation, so test will run out-of-box

Test number #3

Install the following simple test in your Document root and see if “success” appears in your browser.

/MYASP2/www/zend2/htdocs/connection2.inc
<?php
$database            = "*LOCAL"; // *LOCAL on IBM i ... LP0264D on Linux
$cwdatabase          = "localhost";
$user                = "DB2";
$password            = "XXXXXXXX";
$libxmlservice       = "ZENDSVR"; // ZZCALL (Zend Server)
$i5persistentconnect = false;
?>

/MYASP2/www/zend2/htdocs/xxtoolkit_raw.php
<?php
require_once('connection2.inc');
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) echo "Bad connect: $conn,$database,$user,perm=$i5persistentconnect";

$stmt = db2_prepare($conn, "call XMLSERVICE.iPLUG4K(?,?,?,?)");
$ctl  = "*sbmjob"; // *here for no additional private job
$ipc='/tmp/packers';
// $ipc  = "";     // *here no need ipc
$clobIn = "<?xml version='1.0'?>
<pgm name='ZZCALL' lib='$libxmlservice'>
<parm  io='both'>
  <data type='1A'>a</data>
</parm>
<parm  io='both'>
  <data type='1A'>b</data>
</parm>
<parm  io='both'>
  <data type='7p4'>11.1111</data>
</parm>
<parm  io='both'>
  <data type='12p2'>222.22</data>
</parm>
<parm  io='both'>
  <ds>
  <data type='1A'>x</data>
  <data type='1A'>y</data>
  <data type='7p4'>66.6666</data>
  <data type='12p2'>77777.77</data>
  </ds>
</parm>
<return>
  <data type='10i0'>0</data>
</return>
</pgm>";
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
// var_dump($clobOut);
if (strpos($clobOut,"4444444444.44")>0) echo "success";
else echo "fail";
?>

run the test Apache …

Point your browser to php program ...
http://myibmi/xxtoolkit_raw.php
success

run the test 2-tier … I choose to run from command line on my Linux machine, but Linux Apache would also work.

$ which php
/usr/local/zend/bin/php
$ php xxtoolkit_raw.php
success

Ok, next run stress test …

call qp2term
> cd /usr/local/Zend/apache2/bin
> ab -t 25 -c 10 http://lp0264d/xxtoolkit_raw.php
-t 25 -- 25 seconds
-c 10 -- 10 concurrent browsers (simulates ten browsers)
  • this test is designed to drive CPU to 100% (a good thing), so don’t panic about CPU

Level 4 – PHP New Toolkit working

At this level we want to check our PHP CW Toolkit built on top of raw XML Toolkit (Level 3).

Note:

  • Your machine may have document root at /www/zendsvr vs. /MYASP2/www/zend2 (out-of-box installed Zend Server).
  • This same test can be run 2-tier from Linux/Windows to IBM i using DB2 Connect.
  • RPG test *PGM ZENDSVR/ZZCALL is included with Zend Server installation, so test will run out-of-box

Test number #4

Install the following simple test in your Document root and see if “success” appears in your browser.

/MYASP2/www/zend2/htdocs/connection2.inc
<?php
$database            = "*LOCAL"; // *LOCAL on IBM i ... LP0264D on Linux
$cwdatabase          = "localhost";
$user                = "DB2";
$password            = "XXXXXXXX";
$libxmlservice       = "ZENDSVR"; // ZZCALL (Zend Server)
$i5persistentconnect = false;
?>

/MYASP2/www/zend2/htdocs/xxtoolkit_new.php
<?php
require_once('connection2.inc');
require_once("ToolkitService.php");
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) echo "Bad connect: $conn,$database,$user,perm=$i5persistentconnect";

try { $ToolkitServiceObj = ToolkitService::getInstance($conn); }
catch (Exception $e) { die($e->getMessage()); }
$param[] = $ToolkitServiceObj->AddParameterChar   ('both',  1,  'INCHARA', 'var1', 'Y');
$param[] = $ToolkitServiceObj->AddParameterChar   ('both',  1,  'INCHARB', 'var2', 'Z');
$param[] = $ToolkitServiceObj->AddParameterPackDec('both',  7,4,'INDEC1',  'var3', '001.0001');
$param[] = $ToolkitServiceObj->AddParameterPackDec('both', 12,2,'INDEC2',  'var4', '0000000003.04');
  $ds[] = $ToolkitServiceObj->AddParameterChar   ('both',  1,  'DSCHARA', 'ds1',  'A');
  $ds[] = $ToolkitServiceObj->AddParameterChar   ('both',  1,  'DSCHARB', 'ds2',  'B');
  $ds[] = $ToolkitServiceObj->AddParameterPackDec('both',  7,4,'DSDEC1',  'ds3',  '005.0007');
  $ds[] = $ToolkitServiceObj->AddParameterPackDec('both', 12,2,'DSDEC1',  'ds4',  '0000000006.08');
$param[] = $ToolkitServiceObj->AddDataStruct($ds);
$clobOut  = $ToolkitServiceObj->PgmCall('ZZCALL', $libxmlservice, $param, null, null);
// var_dump($clobOut);
$value = "what is ...".$clobOut["io_param"]["ds4"];
if (strpos($value,"4444444444.44")>-1) echo "success";
else echo "fail";

run the test Apache …

Point your browser to php program ...
http://myibmi/xxtoolkit_new.php
success

run the test 2-tier … I choose to run from command line on my Linux machine, but Linux Apache would also work.

$ which php
/usr/local/zend/bin/php
$ php xxtoolkit_new.php
success

Ok, next run stress test …

call qp2term
> cd /usr/local/Zend/apache2/bin
> ab -t 25 -c 10 http://lp0264d/xxtoolkit_new.php
-t 25 -- 25 seconds
-c 10 -- 10 concurrent browsers (simulates ten browsers)
  • this test is designed to drive CPU to 100% (a good thing), so don’t panic about CPU

Level 5 – PHP CW Toolkit working (optional)

At this level we want to check our PHP CW Toolkit built on top of new Toolkit (Level 4).

Note:

  • Your machine may have document root at /www/zendsvr vs. /MYASP2/www/zend2 (out-of-box installed Zend Server).
  • This same test can be run 2-tier from Linux/Windows to IBM i using DB2 Connect.
  • RPG test *PGM ZENDSVR/ZZCALL is included with Zend Server installation, so test will run out-of-box

Test number #5

Install the following simple test in your Document root and see if “success” appears in your browser.

/MYASP2/www/zend2/htdocs/connection2.inc
<?php
$database            = "*LOCAL"; // *LOCAL on IBM i ... LP0264D on Linux
$cwdatabase          = "localhost";
$user                = "DB2";
$password            = "XXXXXXXX";
$libxmlservice       = "ZENDSVR"; // ZZCALL (Zend Server)
$i5persistentconnect = false;
?>

/MYASP2/www/zend2/htdocs/xxtoolkit_cw.php
<?php
require_once('connection2.inc');
require_once('CW/cw.php'); // new toolkit compatibility (Alan)

/* connect */
if ($i5persistentconnect) $conn = i5_pconnect($cwdatabase,$user,$password);
else $conn = i5_connect($cwdatabase,$user,$password);
if (!$conn) echo "Bad connect: $conn,$cwdatabase,$user,perm=$i5persistentconnect";

if (!$conn)
{ $tab = i5_error();
  die("fail Connect: ".$tab[2]." "."$tab[3], $tab[0]");
}

/* prepare */
$description =
array
(
  // single parms
  array
  ( "Name"=>"INCHARA","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_CHAR,"Length"=>"1"),
  array
  ( "Name"=>"INCHARB","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_CHAR,"Length"=>"1"),
  array
  ( "Name"=>"INDEC1","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_PACKED,"Length"=>"7.4"),
  array
  ( "Name"=>"INDEC2","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_PACKED,"Length"=>"12.2"),
  // structure parm
  array
  ( "DSName"=>"INDS1",
    "Count"=>1,
    "DSParm"=>
    array
    (
    array
    ( "Name"=>"DSCHARA","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_CHAR,"Length"=>"1"),
    array
    ( "Name"=>"DSCHARB","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_CHAR,"Length"=>"1"),
    array
    ( "Name"=>"DSDEC1","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_PACKED,"Length"=>"7.4"),
    array
    ( "Name"=>"DSDEC2","IO"=>I5_IN|I5_OUT,"Type"=>I5_TYPE_PACKED,"Length"=>"12.2"),
    )
  )
);
$pgm = i5_program_prepare("$libxmlservice/ZZCALL", $description);
if (!$pgm)
{ $tab = i5_error();
  die("fail Prepare: ".$tab[2]." "."$tab[3], $tab[0]");
}

// *** parameter list allocation
$list=
array
(
  "DSCHARA"=>"x",
  "DSCHARB"=>"y",
  "DSDEC1"=>66.6666,
  "DSDEC2"=>77777.77,
);
// *** parameter values passed to procedure
$in =
array
(
  "INCHARA"=>"a",
  "INCHARB"=>"b",
  "INDEC1"=>11.1111,
  "INDEC2"=>222.22,
  "INDS1"=>$list,
);
// *** name of variables created for out parameters
$out =
array
(
  "INCHARA"=>"INCHARA",
  "INCHARB"=>"INCHARB",
  "INDEC1"=>"INDEC1",
  "INDEC2"=>"INDEC2",
  "INDS1"=>"INDS1",
);
$rc=i5_program_call($pgm, $in, $out);
if ($rc != false)
{
  if ($INCHARA != 'C') die("fail C == $INCHARA\n");
  if ($INCHARB != 'D') die("fail D == $INCHARB\n");
  if ($INDEC1 != 321.1234) die("fail 321.1234 == $INDEC1\n");
  if ($INDEC2 != 1234567890.12) die("fail 1234567890.12 = $INDEC2\n");
  if ($INDS1["DSCHARA"] != 'E'
  ||  $INDS1["DSCHARB"] != 'F'
  ||  $INDS1["DSDEC1"] != 333.333
  ||  $INDS1["DSDEC2"] != 4444444444.44)
  {
    var_dump($INDS1);
    die("fail DS not correct\n");
  }

}
else
{ $tab = i5_error();
  die("fail Call: ".$tab[2]." "."$tab[3], $tab[0]");
}


// good
echo "success";
?>

run the test Apache …

Point your browser to php program ...
http://myibmi/xxtoolkit_cw.php
success

run the test 2-tier … I choose to run from command line on my Linux machine, but Linux Apache would also work.

$ which php
/usr/local/zend/bin/php
$ php xxtoolkit_cw.php
success

Ok, next run stress test …

call qp2term
> cd /usr/local/Zend/apache2/bin
> ab -t 25 -c 10 http://lp0264d/xxtoolkit_cw.php
-t 25 -- 25 seconds
-c 10 -- 10 concurrent browsers (simulates ten browsers)
  • this test is designed to drive CPU to 100% (a good thing), so don’t panic about CPU

Apache ab sample running

Apache ab web site stress tests are performed from the 5250 command line (call qp2term) or ssh myibmi using PASE.

  • You can run stress tests from 2-tier Linux/Windows using Apache ab tool, but i am using Apchae ab from PASE.
  • Apache ab tool is not perfect, but if you use relatively “sane” number of browsers like -c 10 it will work.
  • Apache ab test is designed to drive CPU to 100% (a good thing), so don’t panic about CPU

Example run my machine …

> ab -t 25 -c 10 http://lp0264d/hello.php
This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright 2006 The Apache Software Foundation, http://www.apache.org/

Benchmarking lp0264d (be patient)
Completed 5000 requests
Completed 10000 requests
Completed 15000 requests
Completed 20000 requests
Finished 20325 requests


Server Software:        Apache
Server Hostname:        lp0264d
Server Port:            80

Document Path:          /hello.php
Document Length:        11 bytes

Concurrency Level:      10
Time taken for tests:   25.5119 seconds
Complete requests:      20325
Failed requests:        0
Write errors:           0
Total transferred:      3394275 bytes
HTML transferred:       223575 bytes
Requests per second:    812.83 [#/sec] (mean)    <---     800 hits/second
Time per request:       12.303 [ms] (mean)
Time per request:       1.230 [ms] (mean, across all concurrent requests)
Transfer rate:          132.53 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   1.1      0      30
Processing:     2   11   7.2     10     322
Waiting:        2   10   7.2     10     322
Total:          2   11   7.3     11     322

Percentage of the requests served within a certain time (ms)
  50%     11
  66%     12
  75%     14
  80%     15
  90%     17
  95%     19
  98%     21
  99%     23
100%    322 (longest request)
>

wrkactjob – during Apache ab test you can use refresh on wrkactjob scree to see the php-cgi jobs working

  • you should expect to see multiple php-cgi jobs getting some CPU time as you refresh with F10
                            Work with Active Jobs                     LP0264D
                                                            05/16/12  13:37:25
CPU %:   100.0     Elapsed time:   00:00:00     Active jobs:   295

Type options, press Enter.
  2=Change   3=Hold   4=End   5=Work with   6=Release   7=Display message
  8=Work with spooled files   13=Disconnect ...
                    Current
Opt  Subsystem/Job  User        Type  CPU %  Function        Status
        ZEND2        QTMHHTTP    BCI      .0  PGM-zfcgi        SELW
        ZEND2        QTMHHTTP    BCI      .0  PGM-php-cgi.bi   THDW
        ZEND2        QTMHHTTP    BCI      .0  PGM-php-cgi.bi   THDW
        ZEND2        QTMHHTTP    BCI     2.8  PGM-php-cgi.bi   TIMA
        ZEND2        QTMHHTTP    BCI     1.8  PGM-php-cgi.bi   TIMA
        ZEND2        QTMHHTTP    BCI     2.3  PGM-php-cgi.bi   TIMA
        ZEND2        QTMHHTTP    BCI     2.8  PGM-php-cgi.bi   TIMA
        ZEND2        QTMHHTTP    BCI     1.8  PGM-php-cgi.bi   RUN
        ZEND2        QTMHHTTP    BCI     1.4  PGM-php-cgi.bi   TIMA

Stop XMLSERVICE for debugger attach

WRKACTJOB find XMLSERVICE private mode only

IF you are running a private connection (ipc=’/tmp/packers’), you can simply use WRKACTJOB and locate the XMLSERVICE job and attach debugger. However the next qsysopr message option is also available.

XMLSERVICE QSYSOPR message stop anytime

At times it is useful to stop the XMLSERVICE job to connect a debugger to examine an issue, especially if you are running stateless in the QSQSRVR job.

I often find the simple trick below to be very useful.

The trick …

At this time the PHP wrappers have not implemented a stop for debugger interface (cough … Alan), but XMLSERVICE has control *debug ability already available. In the following example adding $ctl  .= " *debug"; will stop XMLSERVICE job with a inquire message on qsysopr, simply attach your debugger to job # in msg, set a breakpoint in your module (or in XMLSERVICE module), and answer qsysopr message with any character to let XMLSERVICE continue to your breakpoint. That is it …

Hint: PHP wrappers are sending XML just like below, so if you have toolkit.ini debug turned on you can often simply cut/paste your XML in debug log into the variable $clobIn below.

Test #3 as example …

/MYASP2/www/zend2/htdocs/connection2.inc
<?php
$database            = "*LOCAL"; // *LOCAL on IBM i ... LP0264D on Linux
$cwdatabase          = "localhost";
$user                = "DB2";
$password            = "XXXXXXXX";
$libxmlservice       = "ZENDSVR"; // ZZCALL (Zend Server)
$i5persistentconnect = false;
?>

/MYASP2/www/zend2/htdocs/xxtoolkit_raw.php
<?php
require_once('connection2.inc');
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) echo "Bad connect: $conn,$database,$user,perm=$i5persistentconnect";

$stmt = db2_prepare($conn, "call XMLSERVICE.iPLUG4K(?,?,?,?)");
$ctl  = "*sbmjob";         // *here for no additional private job
$ctl  .= " *debug";        // THIS WILL STOP XMLSERVICE JOB MSG TO QSYSOPR (server side)
// $ctl  .= " *debugproc"; // THIS WILL STOP XMLSERVICE/QSQSRVR JOB MSG TO QSYSOPR (client side)
                          // if running *here either *debug/*debugproc will work
$ipc='/tmp/packers';
// $ipc  = "";     // *here no need ipc
$clobIn = "<?xml version='1.0'?>
<pgm name='ZZCALL' lib='$libxmlservice'>
<parm  io='both'>
  <data type='1A'>a</data>
</parm>
<parm  io='both'>
  <data type='1A'>b</data>
</parm>
<parm  io='both'>
  <data type='7p4'>11.1111</data>
</parm>
<parm  io='both'>
  <data type='12p2'>222.22</data>
</parm>
<parm  io='both'>
  <ds>
  <data type='1A'>x</data>
  <data type='1A'>y</data>
  <data type='7p4'>66.6666</data>
  <data type='12p2'>77777.77</data>
  </ds>
</parm>
<return>
  <data type='10i0'>0</data>
</return>
</pgm>";
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
// var_dump($clobOut);
if (strpos($clobOut,"4444444444.44")>0) echo "success";
else echo "fail";
?>

Hint: If $clobIn PHP XML single quote / double quote issues are driving you nuts try the following technique.

BTW – For the enterprising do-it-yourself PHP builder you can see how very easy it would be to make a custom call *PGM API with parameter substitution using str_replace() function similar to test_lib_replace(), something like function ZZCALL($INCHARA,$INCHARB,$INDEC1,$INDEC2,$INDS1)

$xml = <<<ENDPROC
<?xml version='1.0'?>
<script>
<pgm name='ZZCALL' lib='xyzlibxmlservicexyz'>
<parm  io='both'>
  <data type='1A' var='INCHARA'>a</data>
</parm>
<parm  io='both'>
  <data type='1A' var='INCHARB'>b</data>
</parm>
<parm  io='both'>
  <data type='7p4' var='INDEC1'>11.1111</data>
</parm>
<parm  io='both'>
  <data type='12p2' var='INDEC2'>222.22</data>
</parm>
<parm  io='both'>
  <ds>
  <data type='1A' var='INDS1.DSCHARA'>x</data>
  <data type='1A' var='INDS1.DSCHARB'>y</data>
  <data type='7p4' var='INDS1.DSDEC1'>66.6666</data>
  <data type='12p2' var='INDS1.DSDEC2'>77777.77</data>
  </ds>
</parm>
<return>
  <data type='10i0'>0</data>
</return>
</pgm>
</script>
ENDPROC;
$clobIn = test_lib_replace($xml);

// xml common text replacement
function test_lib_replace($xml) {
  global $libxmlservice, $iOPM;
  if (!$iOPM) {
    $was = array("xyzlibxmlservicexyz");
    $now = array("$libxmlservice");
  }
  else {
    $was = array("xyzlibxmlservicexyz","<pgm");
    $now = array("$libxmlservice","<pgm mode='opm'");
  }
  $out = str_replace($was,$now,$xml);
  return $out;
}

XMLSERVICE can be stopped …

Debug technique:

It’s as easy as 1-2-3-4-5-6-7-8-9-10 :)

  1. Add the following line to your PHP script before the program call to be debugged. $toolkitConn should be your toolkit connection object.
``$toolkitConn->setOptions(array('customControl'=>'*debug')).``

Run your script.
The script will "hang" while it waits on #2 below...
(move to green screen 5250 for steps 2-10)
  1. A MSGW inquiry message in DSPMSG QSYSOPR will be generated by the toolkit.
  2. Note the job information (number, name, user) provided in the MSGW.
  3. STRSRVJOB using that job information as parameters.
  4. STRDBG with the program and library you wish to debug.
  5. Answer the MSGW. Any answer will do–“G” is fine.
  6. The RPG program source will appear in debug mode in your terminal, ready to step through, allowing you to inspect variables, etc.
  7. When done inspecting and stepping, let the RPG program complete (using function keys indicated on screen).
  8. ENDDBG
  9. ENDSRVJOB

XMLSERVICE/Toolkit Date, Time, Timestamp

Goto Main Page

Date, Time, Timestamp - what do?

Time and date are easy, correct size character buffer and pass any format you want. Essentially xmlservice/toolkit works without formal knowledge of date, time, timestamp formats, simply passing another set of characters in memory for a parameter (pass-by-reference mostly), called program most likely throws exception when format incompatible (see with any MI debugger).

1) New PHP Toolkit Samples

ZZDATE (D datfmt(*iso))
<?php
require_once('connection.inc');
// assume /usr/local/zendsvr/share/ToolkitAPI
require_once("ToolkitService.php");
// new toolkit
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(
array('InternalKey'=>$internalKey, // route to same XMLSERVICE job /tmp/myjob1
'subsystem'=>"QGPL/QDFTJOBD",      // subsystem/jobd to start XMLSERVICE (if not running)
'plug'=>"iPLUG32K"));              // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//      * zzdate: check date parm
//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//     P zzdate          B                   export
//     D zzdate          PI              D
//     D  myDate                         D   datfmt(*iso)
//      * vars
//     D  retDate        s               D   datfmt(*iso)
//      /free
//       retDate=myDate;
//       myDate=d'2007-09-30';
//       return retDate;
//     /end-free
//     P                 E
$param[] = $ToolkitServiceObj->AddParameterChar   ('both',  10,  'ZZDATE', 'myDate', '2009-05-11');
$retrn[] = $ToolkitServiceObj->AddParameterChar   ('both',  10,  'ZZDATE', 'retDate', '2002-02-02');
$result  = $ToolkitServiceObj->PgmCall('ZZSRV', $libxmlservice, $param, $retrn, array('func'=>'ZZDATE'));
// var_dump($result);
/* in/out param myDate */
$myDate = "XMLSERVICE i/o param myDate: ".$result["io_param"]["myDate"];
echo "$myDate\n";
$expect = '2007-09-30';
if (strpos($myDate,$expect)<1) die("Fail missing $expect\n");
/* return value retDate */
$retDate = "XMLSERVICE return retDate: ".$result["retvals"]["retDate"];
echo "$retDate\n";
$expect = '2009-05-11';
if (strpos($retDate,$expect)<1) die("Fail missing $expect\n");
/* all good */
echo "Success\n";
?>
ZZDATEUSA (D datfmt(*USA))
<?php
require_once('connection.inc');
// assume /usr/local/zendsvr/share/ToolkitAPI
require_once("ToolkitService.php");
// new toolkit
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(
array('InternalKey'=>$internalKey, // route to same XMLSERVICE job /tmp/myjob1
'subsystem'=>"QGPL/QDFTJOBD",      // subsystem/jobd to start XMLSERVICE (if not running)
'plug'=>"iPLUG32K"));              // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//      * zzdateUSA: check date parm
//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//     P zzdateUSA       B                   export
//     D zzdateUSA       PI              D   datfmt(*USA)
//     D  myDate                         D   datfmt(*USA)
//      * vars
//     D  retDate        s               D   datfmt(*USA)
//      /free
//       retDate=myDate;
//       myDate=d'2007-09-30';
//       return retDate;
//      /end-free
//     P                 E
$param[] = $ToolkitServiceObj->AddParameterChar   ('both',  10,  'ZZDATEUSA', 'myDate', '05/11/2009');
$retrn[] = $ToolkitServiceObj->AddParameterChar   ('both',  10,  'ZZDATEUSA', 'retDate', '2002-02-02');
$result  = $ToolkitServiceObj->PgmCall('ZZSRV', $libxmlservice, $param, $retrn, array('func'=>'ZZDATEUSA'));
// var_dump($result);
/* in/out param myDate */
$myDate = "XMLSERVICE i/o param myDate: ".$result["io_param"]["myDate"];
echo "$myDate\n";
$expect = '09/30/2007';
if (strpos($myDate,$expect)<1) die("Fail missing $expect\n");
/* return value retDate */
$retDate = "XMLSERVICE return retDate: ".$result["retvals"]["retDate"];
echo "$retDate\n";
$expect = '05/11/2009';
if (strpos($retDate,$expect)<1) die("Fail missing $expect\n");
/* all good */
echo "Success\n";
?>
ZZTIME (T timfmt(*iso))
<?php
require_once('connection.inc');
// assume /usr/local/zendsvr/share/ToolkitAPI
require_once("ToolkitService.php");
// new toolkit
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(
array('InternalKey'=>$internalKey, // route to same XMLSERVICE job /tmp/myjob1
'subsystem'=>"QGPL/QDFTJOBD",      // subsystem/jobd to start XMLSERVICE (if not running)
'plug'=>"iPLUG32K"));              // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//      * zztime: check time parm
//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//     P zztime          B                   export
//     D zztime          PI              T
//     D  myTime                         T   timfmt(*iso)
//      * vars
//     D  retTime        s               T   timfmt(*iso)
//      /free
//       retTime=myTime;
//       myTime=t'12.34.56';
//       return retTime;
//      /end-free
//     P                 E
$param[] = $ToolkitServiceObj->AddParameterChar   ('both',  8,  'ZZTIME', 'myTime', '09.45.29');
$retrn[] = $ToolkitServiceObj->AddParameterChar   ('both',  8,  'ZZTIME', 'retTime', '02.02.02');
$result  = $ToolkitServiceObj->PgmCall('ZZSRV', $libxmlservice, $param, $retrn, array('func'=>'ZZTIME'));
// var_dump($result);
/* in/out param myDate */
$myTime = "XMLSERVICE i/o param myTime: ".$result["io_param"]["myTime"];
echo "$myTime\n";
$expect = '12.34.56';
if (strpos($myTime,$expect)<1) die("Fail missing $expect\n");
/* return value retTime */
$retTime = "XMLSERVICE return retTime: ".$result["retvals"]["retTime"];
echo "$retTime\n";
$expect = '09.45.29';
if (strpos($retTime,$expect)<1) die("Fail missing $expect\n");
/* all good */
echo "Success\n";
?>
ZZTIME (T timfmt(*USA))
<?php
require_once('connection.inc');
// assume /usr/local/zendsvr/share/ToolkitAPI
require_once("ToolkitService.php");
// new toolkit
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(
array('InternalKey'=>$internalKey, // route to same XMLSERVICE job /tmp/myjob1
'subsystem'=>"QGPL/QDFTJOBD",      // subsystem/jobd to start XMLSERVICE (if not running)
'plug'=>"iPLUG32K"));              // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//      * zztimeUSA: check time parm
//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//     P zztimeUSA       B                   export
//     D zztimeUSA       PI              T   timfmt(*USA)
//     D  myTime                         T   timfmt(*USA)
//      * vars
//     D  retTime        s               T   timfmt(*USA)
//      /free
//       retTime=myTime;
//       myTime=t'12.34.00';
//       return retTime;
//      /end-free
//     P                 E
$param[] = $ToolkitServiceObj->AddParameterChar   ('both',  8,  'ZZTIMEUSA', 'myTime', '09:45 AM');
$retrn[] = $ToolkitServiceObj->AddParameterChar   ('both',  8,  'ZZTIMEUSA', 'retTime', '02:02 PM');
$result  = $ToolkitServiceObj->PgmCall('ZZSRV', $libxmlservice, $param, $retrn, array('func'=>'ZZTIMEUSA'));
// var_dump($result);
/* in/out param myDate */
$myTime = "XMLSERVICE i/o param myTime: ".$result["io_param"]["myTime"];
echo "$myTime\n";
$expect = '12:34 PM';
if (strpos($myTime,$expect)<1) die("Fail missing $expect\n");
/* return value retTime */
$retTime = "XMLSERVICE return retTime: ".$result["retvals"]["retTime"];
echo "$retTime\n";
$expect = '09:45 AM';
if (strpos($retTime,$expect)<1) die("Fail missing $expect\n");
/* all good */
echo "Success\n";
?>
ZZSTAMP (Z)
<?php
require_once('connection.inc');
// assume /usr/local/zendsvr/share/ToolkitAPI
require_once("ToolkitService.php");
// new toolkit
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(
array('InternalKey'=>$internalKey, // route to same XMLSERVICE job /tmp/myjob1
'subsystem'=>"QGPL/QDFTJOBD",      // subsystem/jobd to start XMLSERVICE (if not running)
'plug'=>"iPLUG32K"));              // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//      * zzstamp: check timestamp parm
//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//     P zzstamp         B                   export
//     D zzstamp         PI              Z
//     D  myStamp                        Z
//      * vars
//     D  retStamp       s               Z
//      /free
//       retStamp=myStamp;
//       myStamp=z'1960-12-31-12.32.23.000000';
//       return retStamp;
//      /end-free
//     P                 E
$param[] = $ToolkitServiceObj->AddParameterChar   ('both',  26,  'ZZSTAMP', 'myStamp', '2011-12-29-12.45.29.000000');
$retrn[] = $ToolkitServiceObj->AddParameterChar   ('both',  26,  'ZZSTAMP', 'retStamp', '2002-02-02-02.02.02.000000');
$result  = $ToolkitServiceObj->PgmCall('ZZSRV', $libxmlservice, $param, $retrn, array('func'=>'ZZSTAMP'));
// var_dump($result);
/* in/out param myDate */
$myStamp = "XMLSERVICE i/o param myStamp: ".$result["io_param"]["myStamp"];
echo "$myStamp\n";
$expect = '1960-12-31-12.32.23.000000';
if (strpos($myStamp,$expect)<1) die("Fail missing $expect\n");
/* return value retStamp */
$retStamp = "XMLSERVICE return retStamp: ".$result["retvals"]["retStamp"];
echo "$retStamp\n";
$expect = '2011-12-29-12.45.29.000000';
if (strpos($retStamp,$expect)<1) die("Fail missing $expect\n");
/* all good */
echo "Success\n";
?>

2) XMLSERVICE Raw XML Samples

ZZDATE (D datfmt(*iso))
<?php
// see connection.inc param details ...
require_once('connection.inc');
// call IBM i
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$clobIn = getxml();
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
// -----------------
// output processing
// -----------------
// dump raw XML (easy test debug)
var_dump($clobOut);
// xml check via simplexml vs. expected results
$xmlobj = simplexml_load_string($clobOut);
if (!$xmlobj) die("Bad XML returned");
$allpgms = $xmlobj->xpath('/script/pgm');
if (!$allpgms) die("Missing XML pgm info");
// -----------------
// output pgm call
// -----------------
// only one program this XML script
$pgm = $allpgms[0];
$name = $pgm->attributes()->name;
$lib  = $pgm->attributes()->lib;
$func = $pgm->attributes()->func;
// pgm parms
$parm = $pgm->xpath('parm');
if (!$parm) die("Fail XML pgm parms missing ($lib/$name.$func)\n");
$var  = $parm[0]->data->attributes()->var;
$actual = (string)$parm[0]->data;
$expect='2007-09-30';
if ($actual != $expect) die("parm: $var ($actual not $expect) ($lib/$name.$func)\n");
// pgm data returned
$retn = $pgm->xpath('return');
if (!$retn) die("Fail XML pgm return missing ($lib/$name.$func)\n");
$var  = $retn[0]->data->attributes()->var;
$actual = (string)$retn[0]->data;
$expect='2009-05-11';
if ($actual != $expect) die("return: $var ($actual not $expect) ($lib/$name.$func)\n");

// good
echo "Success ($lib/$name.$func)\n";

//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//      * zzdate: check date parm
//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//     P zzdate          B                   export
//     D zzdate          PI              D
//     D  myDate                         D   datfmt(*iso)
function getxml() {
$clob = <<<ENDPROC
<?xml version='1.0'?>
<script>
<pgm name='ZZSRV' lib='xyzlibxmlservicexyz' func='ZZDATE'>
<parm  io='both'>
  <data var='myDate' type='10A'>2009-05-11</data>
</parm>
<return>
  <data var='myDateRet' type='10A'>nada</data>
</return>
</pgm>
</script>
ENDPROC;
return test_lib_replace($clob);
}
?>
ZZDATEUSA (D datfmt(*USA))
<?php
// see connection.inc param details ...
require_once('connection.inc');
// call IBM i
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$clobIn = getxml();
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
// -----------------
// output processing
// -----------------
// dump raw XML (easy test debug)
var_dump($clobOut);
// xml check via simplexml vs. expected results
$xmlobj = simplexml_load_string($clobOut);
if (!$xmlobj) die("Bad XML returned");
$allpgms = $xmlobj->xpath('/script/pgm');
if (!$allpgms) die("Missing XML pgm info");
// -----------------
// output pgm call
// -----------------
// only one program this XML script
$pgm = $allpgms[0];
$name = $pgm->attributes()->name;
$lib  = $pgm->attributes()->lib;
$func = $pgm->attributes()->func;
// pgm parms
$parm = $pgm->xpath('parm');
if (!$parm) die("Fail XML pgm parms missing ($lib/$name.$func)\n");
$var  = $parm[0]->data->attributes()->var;
$actual = (string)$parm[0]->data;
$expect='09/30/2007';
if ($actual != $expect) die("parm: $var ($actual not $expect) ($lib/$name.$func)\n");
// pgm data returned
$retn = $pgm->xpath('return');
if (!$retn) die("Fail XML pgm return missing ($lib/$name.$func)\n");
$var  = $retn[0]->data->attributes()->var;
$actual = (string)$retn[0]->data;
$expect='05/11/2009';
if ($actual != $expect) die("return: $var ($actual not $expect) ($lib/$name.$func)\n");

// good
echo "Success ($lib/$name.$func)\n";

//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//      * zzdateUSA: check date parm
//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//     P zzdateUSA       B                   export
//     D zzdateUSA       PI              D   datfmt(*USA)
//     D  myDate                         D   datfmt(*USA)
function getxml() {
$clob = <<<ENDPROC
<?xml version='1.0'?>
<script>
<pgm name='ZZSRV' lib='xyzlibxmlservicexyz' func='ZZDATEUSA'>
<parm  io='both'>
  <data var='myDate' type='10A'>05/11/2009</data>
</parm>
<return>
  <data var='myDateRet' type='10A'>nada</data>
</return>
</pgm>
</script>
ENDPROC;
return test_lib_replace($clob);
}
?>
ZZTIME (T timfmt(*iso))
<?php
// see connection.inc param details ...
require_once('connection.inc');
// call IBM i
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$clobIn = getxml();
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
// -----------------
// output processing
// -----------------
// dump raw XML (easy test debug)
var_dump($clobOut);
// xml check via simplexml vs. expected results
$xmlobj = simplexml_load_string($clobOut);
if (!$xmlobj) die("Bad XML returned");
$allpgms = $xmlobj->xpath('/script/pgm');
if (!$allpgms) die("Missing XML pgm info");
// -----------------
// output pgm call
// -----------------
// only one program this XML script
$pgm = $allpgms[0];
$name = $pgm->attributes()->name;
$lib  = $pgm->attributes()->lib;
$func = $pgm->attributes()->func;
// pgm parms
$parm = $pgm->xpath('parm');
if (!$parm) die("Fail XML pgm parms missing ($lib/$name.$func)\n");
$var  = $parm[0]->data->attributes()->var;
$actual = (string)$parm[0]->data;
$expect='12.34.56';
if ($actual != $expect) die("parm: $var ($actual not $expect) ($lib/$name.$func)\n");
// pgm data returned
$retn = $pgm->xpath('return');
if (!$retn) die("Fail XML pgm return missing ($lib/$name.$func)\n");
$var  = $retn[0]->data->attributes()->var;
$actual = (string)$retn[0]->data;
$expect='09.45.29';
if ($actual != $expect) die("return: $var ($actual not $expect) ($lib/$name.$func)\n");

// good
echo "Success ($lib/$name.$func)\n";

//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//      * zztime: check time parm
//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//     P zztime          B                   export
//     D zztime          PI              T
//     D  myTime                         T   timfmt(*iso)
function getxml() {
$clob = <<<ENDPROC
<?xml version='1.0'?>
<script>
<pgm name='ZZSRV' lib='xyzlibxmlservicexyz' func='ZZTIME'>
<parm  io='both'>
  <data var='myTime' type='8A'>09.45.29</data>
</parm>
<return>
  <data var='myTimeRet' type='8A'>nada</data>
</return>
</pgm>
</script>
ENDPROC;
return test_lib_replace($clob);
}
?>
ZZTIMEUSA (T timfmt(*USA))
<?php
// see connection.inc param details ...
require_once('connection.inc');
// call IBM i
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$clobIn = getxml();
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
// -----------------
// output processing
// -----------------
// dump raw XML (easy test debug)
var_dump($clobOut);
// xml check via simplexml vs. expected results
$xmlobj = simplexml_load_string($clobOut);
if (!$xmlobj) die("Bad XML returned");
$allpgms = $xmlobj->xpath('/script/pgm');
if (!$allpgms) die("Missing XML pgm info");
// -----------------
// output pgm call
// -----------------
// only one program this XML script
$pgm = $allpgms[0];
$name = $pgm->attributes()->name;
$lib  = $pgm->attributes()->lib;
$func = $pgm->attributes()->func;
// pgm parms
$parm = $pgm->xpath('parm');
if (!$parm) die("Fail XML pgm parms missing ($lib/$name.$func)\n");
$var  = $parm[0]->data->attributes()->var;
$actual = (string)$parm[0]->data;
$expect='12:34 PM';
if ($actual != $expect) die("parm: $var ($actual not $expect) ($lib/$name.$func)\n");
// pgm data returned
$retn = $pgm->xpath('return');
if (!$retn) die("Fail XML pgm return missing ($lib/$name.$func)\n");
$var  = $retn[0]->data->attributes()->var;
$actual = (string)$retn[0]->data;
$expect='09:45 AM';
if ($actual != $expect) die("return: $var ($actual not $expect) ($lib/$name.$func)\n");

// good
echo "Success ($lib/$name.$func)\n";

//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//      * zztimeUSA: check time parm
//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//     P zztimeUSA       B                   export
//     D zztimeUSA       PI              T   timfmt(*USA)
//     D  myTime                         T   timfmt(*USA)
function getxml() {
$clob = <<<ENDPROC
<?xml version='1.0'?>
<script>
<pgm name='ZZSRV' lib='xyzlibxmlservicexyz' func='ZZTIMEUSA'>
<parm  io='both'>
  <data type='8A'>09:45 AM</data>
</parm>
<return>
  <data type='8A'>nada</data>
</return>
</pgm>
</script>
ENDPROC;
return test_lib_replace($clob);
}
?>
ZZSTAMP (Z)
<?php
// see connection.inc param details ...
require_once('connection.inc');
// call IBM i
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$clobIn = getxml();
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
// -----------------
// output processing
// -----------------
// dump raw XML (easy test debug)
var_dump($clobOut);
// xml check via simplexml vs. expected results
$xmlobj = simplexml_load_string($clobOut);
if (!$xmlobj) die("Bad XML returned");
$allpgms = $xmlobj->xpath('/script/pgm');
if (!$allpgms) die("Missing XML pgm info");
// -----------------
// output pgm call
// -----------------
// only one program this XML script
$pgm = $allpgms[0];
$name = $pgm->attributes()->name;
$lib  = $pgm->attributes()->lib;
$func = $pgm->attributes()->func;
// pgm parms
$parm = $pgm->xpath('parm');
if (!$parm) die("Fail XML pgm parms missing ($lib/$name.$func)\n");
$var  = $parm[0]->data->attributes()->var;
$actual = (string)$parm[0]->data;
$expect='1960-12-31-12.32.23.000000';
if ($actual != $expect) die("parm: $var ($actual not $expect) ($lib/$name.$func)\n");
// pgm data returned
$retn = $pgm->xpath('return');
if (!$retn) die("Fail XML pgm return missing ($lib/$name.$func)\n");
$var  = $retn[0]->data->attributes()->var;
$actual = (string)$retn[0]->data;
$expect='2011-12-29-12.45.29.000000';
if ($actual != $expect) die("return: $var ($actual not $expect) ($lib/$name.$func)\n");

// good
echo "Success ($lib/$name.$func)\n";

//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//      * zzstamp: check timestamp parm
//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//     P zzstamp         B                   export
//     D zzstamp         PI              Z
//     D  myStamp                        Z
function getxml() {
$clob = <<<ENDPROC
<?xml version='1.0'?>
<script>
<pgm name='ZZSRV' lib='xyzlibxmlservicexyz' func='ZZSTAMP'>
<parm  io='both'>
  <data var='myStamp' type='26A'>2011-12-29-12.45.29.000000</data>
</parm>
<return>
  <data var='myStampRet' type='26A'>nada</data>
</return>
</pgm>
</script>
ENDPROC;
return test_lib_replace($clob);
}
?>

XMLSERVICE/Toolkit *LIBL

Goto Main Page

change XMLSERVICE 1.7.3+

The default setting for SBMJOB of all XMLSERVICE jobs was changed to INLLIBL(*CURRENT) better follow the user profile of QSQSRVR job. If you are having difficulty with private connections libl you may want to try this version.

setting *LIBL - what do i do?

Few other topics dealing with web programs cause more frustrations then setting IBM i library list (*LIBL), but if you follow a few rules it all works.

*LIBL conflict between web scripts and IBM i PGMs

Any good conflict needs a basic difference in philosophy causing all the trouble.

  • ‘’’Stateless’’’ - web scripts typically wish to run stateless (PHP), with no environmental settings (such as *LIBL) set on the server
  • ‘’’State Full’’’ - IBM PGMs generally run state full (RPG/Cobol/CLP), meaning need *LIBL to run at all

Rule of toolkit web *LIBL …

The following rules apply to all toolkit DB2 connections persistent (db2_pconnect, odbc_pconnect) and non-persistent (db2_connect, odbc_connect).

  • ‘’’Stateless’’’ - PHP scripts using Toolkit without internalKey (IPC) MUST set *LIBL on every request before calling PGMs (see specific examples)
  • ‘’’State Full’’’ - PHP scripts using Toolkit with internalKey (IPC) set *LIBL only once for life of job before calling PGMs (see specific examples)

1) Specific examples for New PHP Toolkit

  • ‘’’Stateless’’’ - set *LIBL every single request
// *** stateless occurs when no internalKey (e.g. '/tmp/packers') was specified ***
// *** also when ->setToolkitServiceParams('stateless'=>true)) is called
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(array(
'plug'=>"iPLUG32K"));         // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)

// stateless - MUST do this every single script after connect/getInstance()
//             even if using persistent connections (db2_pconnect, odbc_pconnect)
$ToolkitServiceObj->CLCommand("CHGLIBL LIBL(FREDFLIN WILMAFLIN) CURLIB(FREDFLIN)");

// another option might be to call a setup program that sets *LIBL for you.
  • ‘’’State Full’’’ - set *LIBL once and forget it
$internalKey = '/tmp/packers';
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(array(
'InternalKey'=>$internalKey,  // *** RIGHT HERE internalKey/IPC
                            // *** run state full ...
                            //     use SBMJOB command run in new job
                            //     PHP can call again, again, again
                            //     with /tmp/packers and get ...
                            //     same job every time
                            //     same library list (*LIBL)
                            //     same PGMs with open files, etc.
                            //     ... exactly like 5250 sign-on screen
'plug'=>"iPLUG32K"));         // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)


// state full - MUST do this ONCE ONLY after start/sbmjob of XMLSERVICE job
//              then forget about it (unless you choose to change libl) ...
$ToolkitServiceObj->CLCommand("CHGLIBL LIBL(FREDFLIN WILMAFLIN) CURLIB(FREDFLIN)");
  • ‘’’persistent connections’’’ - later releases PHP Toolkit allow reused/shared connections with other work, including persistent connections, but internalKey (IPC) rules remain the same
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);

try { $ToolkitServiceObj = ToolkitService::getInstance($conn); }
catch (Exception $e) { die($e->getMessage()); }

$internalKey = '/tmp/packers';
$ToolkitServiceObj->setToolkitServiceParams(array(
'InternalKey'=>$internalKey,  // *** RIGHT HERE internalKey/IPC
                            // *** run state full ...
                            //     use SBMJOB command run in new job
                            //     PHP can call again, again, again
                            //     with /tmp/packers and get ...
                            //     same job every time
                            //     same library list (*LIBL)
                            //     same PGMs with open files, etc.
                            //     ... exactly like 5250 sign-on screen
'plug'=>"iPLUG32K"));         // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
  • ‘’’plug size’’’ - later releases PHP Toolkit 1.2.4+ calculates the correct plug name via ‘plugSize’, taking db type (ODBC or DB2) into account.
if ($userPickFast) $conn = db2_pconnect($database,$user,$password);
else $conn = odbc_pconnect($database,$user,$password);

try { $ToolkitServiceObj = ToolkitService::getInstance($conn); }
catch (Exception $e) { die($e->getMessage()); }

$internalKey = '/tmp/packers';
$ToolkitServiceObj->setToolkitServiceParams(array(
'InternalKey'=>$internalKey,  // *** RIGHT HERE internalKey/IPC
                            // *** run state full ...
'plugSize'=>"32K"));          // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)

2) Specific examples for XMLSERVICE

  • ‘’’Stateless’’’ - set *LIBL every single request
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$ipc = "";       // *** RIGHT HERE MISSING internalKey/IPC
$ctl = "*here";  // *** run stateless ...
                //     here in any available database job
                //     must set *LIBL evey time
// stateless - MUST do this every single script after connect/getInstance()
//             even if using persistent connections (db2_pconnect, odbc_pconnect)
$clobIn =
"<?xml version='1.0'?>
<script>
<cmd>CHGLIBL LIBL(FREDFLIN WILMAFLIN) CURLIB(FREDFLIN)</cmd>
</script>";
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
  • ‘’’State Full’’’ - set *LIBL once and forget it
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$ipc = "/tmp/packers"; // *** RIGHT HERE internalKey/IPC
$ctl = "*sbmjob";      // *** run state full ...
                    //     use SBMJOB command run in new job
                    //     PHP can call again, again, again
                    //     with /tmp/packers and get ...
                    //     same job every time
                    //     same library list (*LIBL)
                    //     same PGMs with open files, etc.
                    //     ... exactly like 5250 sign-on screen
// state full - MUST do this ONCE ONLY after start/sbmjob of XMLSERVICE job
//              then forget about it (unless you choose to change libl) ...
$clobIn =
"<?xml version='1.0'?>
<script>
<cmd>CHGLIBL LIBL(FREDFLIN WILMAFLIN) CURLIB(FREDFLIN)</cmd>
</script>";
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);

3) Specific examples for New PHP Toolkit CW layer

  • ‘’’Stateless’’’ - set *LIBL every single request, but for CW, specify libraries in reverse order (via ADDLIBLE)
$options = array(I5_OPTIONS_INITLIBL => 'WILMAFLIN FREDFLIN' );
$conn = i5_connect($host, $user, $password, $options);
// or persistent (which is also stateless): still must specify library each time
$conn = i5_pconnect($host, $user, $password, $options);
  • ‘’’State Full’’’ - set *LIBL once and forget it
session_start();

// if we previously saved a connection number in PHP session, use it.
// otherwise, use 0 (which means create a new connection)
$conNum = (isset($_SESSION['conectionNum']) ? $_SESSION['conectionNum'] : 0;

// I5_OPTIONS_PRIVATE_CONNECTION:  connection is private for the session
// I5_OPTIONS_IDLE_TIMEOUT: After a delay of the specified number of seconds with no activity, the job will end.
$options = array(I5_OPTIONS_PRIVATE_CONNECTION => $conNum,
                I5_OPTIONS_IDLE_TIMEOUT       => "60");

// connect as a private connection, starting with a persistent conn
$conn = i5_pconnect ($host, $user, $password, $options);

if (!$conn) {
    echo "Something went wrong: <PRE>" . print_r(i5_error(), true) . "</PRE>";
    $_SESSION['conectionNum'] = 0; // reset number
} else {
    // connected successfully

    // if original conNum was 0, let's retrieve the new number.
    if ($conNum == 0) {

        // Session variable was 0: Get connection ID and store it in session variable.
        $ret = i5_get_property(I5_PRIVATE_CONNECTION, $conn);

        if (!$ret) {
            echo "Something went wrong: <PRE>" . print_r(i5_error(), true) . "</PRE>";
        } else {

            // We have a good new private connection. Store connection ID in session variable
            $_SESSION['conectionNum'] = $ret;

            // and set library list, too.
            i5_command("CHGLIBL LIBL(FREDFLIN WILMAFLIN) CURLIB(FREDFLIN)");
        }
    }
}

XMLSERVICE/Toolkit Sharing QTEMP

Goto Main Page

Sharing QTEMP - what do?

Sharing QTEMP ibm_db2/odbc/pdo_ibm/RPG/xmlservice has a few quirks, but can be “mostly” done if you understand the jobs in play.

The big picture …

xmlservice |--LUW/PASE------|-----------------------IBM i----------------------
run type   |job 1 (1-2 tier)|  job 2 (stateless)        job 3 (IPC/private)
-------|----------------|------------------------|-------------------------
stateless  | php-client-----|-->QSQSRVR              |
          |                | ->ibm_db2,pdo_ibm,odbc |
          |                |   *iPLUGxxxx->         |
          |                | ->XMLSERVICE<>RPG      |
          |                | ->XMLSERVICE<>DB2 (xml)|
          |                | ->QTEMP (yes share)    |

IPC/private| php-client-----|--->QSQSRVR             |
          |                |  ->ibm_db2,pdo_ibm,odbc|
          |                |    *iPLUGxxxx----------|-->XMLSERVICE<>RPG
          |                |  ->QTEMP (no share)    | ->XMLSERVICE<>DB2 (xml)
          |                |                        | ->QTEMP (yes share)

Note: iPLUGxxx stored procedures are included with xmlservice download
  • php-client (job 1) - can be 1-tier (PASE) / 2-tier (Linux/Unix/Windows) and PHP using extension ibm_db2,pdo_ibm,odbc.

  • ibm_db2,pdo_ibm,odbc (job 2 ONLY) - can ONLY “share” QTEMP when running stateless XMLSERVICE<>RPG mode, therefore no IPC/private.

    • PHP ibm_db2,pdo_ibm,odbc are shown in stateless column (job 2), so that everyone is visually reminded that IBM i DB2 actions do not really occur in php-client (job 1), but actually run in an “acquired” DB2 pre-started job (ie. 1-tier/2-tier makes no difference).

      • 2-tier - “true” IBM i connection/job maybe QSQ (PASE), QWI (LUW), etc., but picture above remains consistent behavior for “sharing” QTEMP (job 2).

      • persistent connect - persistent connection (db2_pconnect/odbc_pconnect) or full connection (db2_connect/odbc_connect) makes no difference, big picture remains exactly the same for both connections. Of course you will run faster with persistent connections (up to 30-40%), due to QSQ server job already up and waiting, but you cannot rely on persistent connections to reach same QSQ job each script start / browser click.

        • exception - PASE ibm_db2 provides an obscure “in-line” mode only a few people understand where ibm_db2.ini file set to ibm_db2.i5_ignore_userid=1 will run all DB2 work under the default web profile and stateless in PASE php-client (job 1) … but only if pure PASE ibm_db2 site (no PASE odbc/pdo_ibm) … and … well gee … you are an expert if you understand i5_ignore_userid mode and you probably don’t need this documentation.
        • Note to future self ibm_db2, etc. … just like xmlservice has idle timeout today (1/2 hour), we really should have a timeout idle for persistent connections across db2.
  • XMLSERVICE<>RPG (job 2 or job 3) - is toolkit call to RPG program wishing to “share” QTEMP.

    • Stateless (job 2) - Generally run slower with more CPU challenges, re-started xmlservice running in “acquired” QSQ job under stored procedure call iPLUGxxx, but on return iPLUGxxx/xmlservice need to close up shop and lose all caches (shut down, wake up, shut down, wake up, …).

      • YES stateless - ibm_db2/odbc/pdo_ibm (job 2) CAN share QTEMP with XMLSERVICE<>RPG/XMLSERVICE<>DB2 (also job 2).
    • IPC/private (job 3) - Generally run faster with less CPU challenges, because xmlservice stays running until explicitly killed or idle timeout (1/2 hour default / user controlled).

      • NO IPC/private - ibm_db2/odbc/pdo_ibm (job 2) can NOT share QTEMP with XMLSERVICE<>RPG/XMLSERVICE<>DB2 (job 3).
  • XMLSERVICE<>DB2 (job 2 or job 3) - is NOT yet available in toolkit wrapper (Alan), but RAW xmlservice interface allows xml based DB2 queries and therefore can “share” QTEMP (available today in RAW xmlservice mode).

<?xml version='1.0'?>
<script>
<sql>
<query>
DECLARE GLOBAL TEMPORARY TABLE animalq(
id integer, breed varchar(32), name char(16),
weight decimal(7,2), height numeric(9,2))
ON COMMIT PRESERVE ROWS
</query>
<prepare>
insert into
animalq (id, breed, name, weight, height)
values (?,?,?,?,?)
</prepare>
<execute>
<parm io='in'>1</parm>
<parm io='in'>cat</parm>
<parm io='in'>Pook</parm>
<parm io='in'>3.2</parm>
<parm io='in'>9.56</parm>
</execute>
<execute>
<parm io='in'>2</parm>
<parm io='in'>dog</parm>
<parm io='in'>Peaches</parm>
<parm io='in'>12.3</parm>
<parm io='in'>22.65</parm>
</execute>
<prepare>select * from animalq where WEIGHT > 1.0</prepare>
<execute/>
<describe desc='col'/>
<fetch block='all' desc='on'/>
</sql>
</script>

1) Specific examples for New PHP Toolkit

-----------------------------------------
|              Browser                  |
|---------------------------------------|
| Download RPG (1) | Download PHP (2)   |
| 1) XMLSERVICE    | a) PHP CW Toolkit  |
|    HTML/XML/REST | b) New PHP Toolkit |<- cw-tk-php-x.x.x.zip (*)
|    no PHP        |--------------------|
|    (xmlcgi.pgm)  | c) PHP “Raw XML”   |
|    (optional)    |   (ibm_db2, odbc)  |
|    -----------------------------------|
| 2) XMLSERVICE DB2 stored procedures   |
|    (iPLUG4K, iPLUG32K, ..., iPLUG15M) |
| 3) XMLSERVICE (xmlservice.pgm)        |
|    call most anything on IBM i ...    |
|    (PGM, SRVPGM, PASE, DB2, etc.)     |
------------------------------------------
  • Stateless - connection (job 1 / job 2)
// *** stateless occurs when no internalKey (e.g. '/tmp/packers') was specified ***
// *** also when ->setToolkitServiceParams('stateless'=>true)) is called
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(array(
'plug'=>"iPLUG32K"));         // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
  • State Full - connection (job 1 / job 2 / job 3)
$internalKey = '/tmp/packers';
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(array(
'InternalKey'=>$internalKey,  // *** RIGHT HERE internalKey/IPC
                              // *** run state full ...
                              //     use SBMJOB command run in new job
                              //     PHP can call again, again, again
                              //     with /tmp/packers and get ...
                              //     same job every time
                              //     same library list (*LIBL)
                              //     same PGMs with open files, etc.
                              //     ... exactly like 5250 sign-on screen
'plug'=>"iPLUG32K"));         // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
  • persistent connections - later releases PHP Toolkit allow reused/shared connections with other work, including persistent connections, but internalKey (IPC) rules remain the same (above)
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);

try { $ToolkitServiceObj = ToolkitService::getInstance($conn); }
catch (Exception $e) { die($e->getMessage()); }

2) Specific examples for XMLSERVICE

-----------------------------------------
|              Browser                  |
|---------------------------------------|
| Download RPG (1) | Download PHP (2)   |
| 1) XMLSERVICE    | a) PHP CW Toolkit  |
|    HTML/XML/REST | b) New PHP Toolkit |
|    no PHP        |--------------------|
|    (xmlcgi.pgm)  | c) PHP “Raw XML”   |<- Zend Server for IBM i or Linux or Windows (*)
|    (optional)    |   (ibm_db2, odbc)  |
|    -----------------------------------|
| 2) XMLSERVICE DB2 stored procedures   |
|    (iPLUG4K, iPLUG32K, ..., iPLUG15M) |
| 3) XMLSERVICE (xmlservice.pgm)        |
|    call most anything on IBM i ...    |
|    (PGM, SRVPGM, PASE, DB2, etc.)     |
------------------------------------------
  • Stateless - (job 1 / job 2)
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$ipc = "";       // *** RIGHT HERE MISSING internalKey/IPC
$ctl = "*here";  // *** run stateless ...
               //     here in any available database job
               //     must set *LIBL evey time
// stateless - MUST do this every single script after connect/getInstance()
//             even if using persistent connections (db2_pconnect, odbc_pconnect)
$clobIn =
"<?xml version='1.0'?>
<script>
<cmd>CHGLIBL LIBL(FREDFLIN WILMAFLIN) CURLIB(FREDFLIN)</cmd>
</script>";
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
  • State Full - (job 1 / job 2 / job 3)
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$ipc = "/tmp/packers"; // *** RIGHT HERE internalKey/IPC
$ctl = "*sbmjob";      // *** run state full ...
                    //     use SBMJOB command run in new job
                    //     PHP can call again, again, again
                    //     with /tmp/packers and get ...
                    //     same job every time
                    //     same library list (*LIBL)
                    //     same PGMs with open files, etc.
                    //     ... exactly like 5250 sign-on screen
// state full - MUST do this ONCE ONLY after start/sbmjob of XMLSERVICE job
//              then forget about it (unless you choose to change libl) ...
$clobIn =
"<?xml version='1.0'?>
<script>
<cmd>CHGLIBL LIBL(FREDFLIN WILMAFLIN) CURLIB(FREDFLIN)</cmd>
</script>";
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);

XMLSERVICE/Toolkit Character Varying

Goto Main Page

Character varying - what do?

Character varying is easy, just add character varying attribute. Varying character works for parameters, return and data structures (DS).

1) New PHP Toolkit

-----------------------------------------
|              Browser                  |
|---------------------------------------|
| Download RPG (1) | Download PHP (2)   |
| 1) XMLSERVICE    | a) PHP CW Toolkit  |
|    HTML/XML/REST | b) New PHP Toolkit |<- cw-tk-php-x.x.x.zip (*)
|    no PHP        |--------------------|
|    (xmlcgi.pgm)  | c) PHP “Raw XML”   |
|    (optional)    |   (ibm_db2, odbc)  |
|    -----------------------------------|
| 2) XMLSERVICE DB2 stored procedures   |
|    (iPLUG4K, iPLUG32K, ..., iPLUG15M) |
| 3) XMLSERVICE (xmlservice.pgm)        |
|    call most anything on IBM i ...    |
|    (PGM, SRVPGM, PASE, DB2, etc.)     |
------------------------------------------

Character varying (S 20A varying)

<?php
require_once('connection.inc');
// assume /usr/local/zendsvr/share/ToolkitAPI
require_once("ToolkitService.php");
// new toolkit
try { $ToolkitServiceObj = ToolkitService::getInstance($database, $user, $password); }
catch (Exception $e) { die($e->getMessage()); }
$ToolkitServiceObj->setToolkitServiceParams(
array('InternalKey'=>$internalKey, // route to same XMLSERVICE job /tmp/myjob1
'subsystem'=>"QGPL/QDFTJOBD",      // subsystem/jobd to start XMLSERVICE (if not running)
'plug'=>"iPLUG32K"));              // max size data i/o (iPLUG4K,32K,65K.512K,1M,5M,10M,15M)
//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//      * zzvary: check return varying
//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//     P zzvary          B                   export
//     D zzvary          PI            20A   varying
//     D  myName                       10A   varying
//      * vars
//     D tmp             S             20A   varying
//      /free
//       tmp = 'my name is ';
//       tmp = tmp + myName;
//       return tmp;
//      /end-free
//     P                 E
$param[] = $ToolkitServiceObj->AddParameterChar('both', 10, 'ZZVARY', 'myVary', 'Ranger', 'on'); // 6th parameter--'on'--is for varying
$retrn[] = $ToolkitServiceObj->AddParameterChar('both', 20, 'ZZVARY', 'retVary', 'Mud', 'on'); // 6th parameter--'on'--is for varying
$result  = $ToolkitServiceObj->PgmCall('ZZSRV', $libxmlservice, $param, $retrn, array('func'=>'ZZVARY'));
// var_dump($result);
/* in/out param myDate */
$myVary = "XMLSERVICE i/o param myVary: ".$result["io_param"]["myVary"];
echo "$myVary\n";
$expect = 'Ranger';
if (strpos($myVary,$expect)<1) die("Fail missing $expect\n");
/* return value retVary */
$retVary = "XMLSERVICE return retVary: ".$result["retvals"]["retVary"];
echo "$retVary\n";
$expect = 'my name is Ranger';
if (strpos($retVary,$expect)<1) die("Fail missing $expect\n");
/* all good */
echo "Success\n";
?>

2) XMLSERVICE Raw XML

-----------------------------------------
|              Browser                  |
|---------------------------------------|
| Download RPG (1) | Download PHP (2)   |
| 1) XMLSERVICE    | a) PHP CW Toolkit  |
|    HTML/XML/REST | b) New PHP Toolkit |
|    no PHP        |--------------------|
|    (xmlcgi.pgm)  | c) PHP “Raw XML”   |<- Zend Server for IBM i or Linux or Windows (*)
|    (optional)    |   (ibm_db2, odbc)  |
|    -----------------------------------|
| 2) XMLSERVICE DB2 stored procedures   |
|    (iPLUG4K, iPLUG32K, ..., iPLUG15M) |
| 3) XMLSERVICE (xmlservice.pgm)        |
|    call most anything on IBM i ...    |
|    (PGM, SRVPGM, PASE, DB2, etc.)     |
------------------------------------------

Character varying (S 20A varying)

<?php
// see connection.inc param details ...
require_once('connection.inc');
// call IBM i
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG32K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());
$clobIn = getxml();
$clobOut = "";
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
if (!$ret) die("Bad execute: ".db2_stmt_errormsg());
// -----------------
// output processing
// -----------------
// dump raw XML (easy test debug)
var_dump($clobOut);
// xml check via simplexml vs. expected results
$xmlobj = simplexml_load_string($clobOut);
if (!$xmlobj) die("Bad XML returned");
$allpgms = $xmlobj->xpath('/script/pgm');
if (!$allpgms) die("Missing XML pgm info");
// -----------------
// output pgm call
// -----------------
// only one program this XML script
$pgm = $allpgms[0];
$name = $pgm->attributes()->name;
$lib  = $pgm->attributes()->lib;
$func = $pgm->attributes()->func;
// pgm parms
$parm = $pgm->xpath('parm');
if ($parm) die("Unexpected XML pgm parms io='in' ($lib/$name.$func)\n");
// pgm data returned
$retn = $pgm->xpath('return');
if (!$retn) die("Fail XML pgm return missing ($lib/$name.$func)\n");
$var  = $retn[0]->data->attributes()->var;
$actual = (string)$retn[0]->data;
$expect = 'my name is Ranger';
if ($actual != $expect) die("$var ($actual not $expect) ($lib/$name.$func)\n");

// good
echo "Success ($lib/$name.$func)\n";

//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//      * zzvary: check return varying
//      *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//     P zzvary          B                   export
//     D zzvary          PI            20A   varying
//     D  myName                       10A   varying
//      * vars
//     D tmp             S             20A   varying
//      /free
//       tmp = 'my name is ';
//       tmp = tmp + myName;
//       return tmp;
//      /end-free
//     P                 E
function getxml() {
$clob = <<<ENDPROC
<?xml version='1.0'?>
<script>
<pgm name='ZZSRV' lib='xyzlibxmlservicexyz' func='ZZVARY'>
<parm comment='search this name' io='in'>
<data var='myName' type='10A' varying='on'>Ranger</data>
</parm>
<return>
<data var='myReturnName' type='20A' varying='on'>Mud</data>
</return>
</pgm>
</script>
ENDPROC;
return test_lib_replace($clob);
}
?>

XMLSERVICE/Toolkit CCSID

Goto Main Page

XMLSERVICE should just work

In general if you are using DB2 connections 1/2 tier and settings for your Apache or user profile are valid (not 65535), you will not need any additional CCSID manipulation/configuration.

CCSID - what do?

Few other topics dealing with web programs cause more frustrations over IBM i CCSID settings.

CCSID conflict between web scripts (clients) and IBM i PGMs (server)

Any good conflict needs a basic difference in philosophy causing all the trouble.

  • Client (ASCII) - web scripts typically wish to run ASCII 819/1208 (PHP), code set(s) laptop
  • Server (EBCDIC) - IBM i PGMs generally run EBCDIC (RPG/Cobol/CLP), code set(s) main frame

CCSID rule – always a conversion client/server

The basic operational code set differences between client (ASCII) / server (EBCDIC) required a conversion for all character data moving between client/server to be of value on either side. Your CCSID setting options are many, experimentation is usually required, but perhaps this web page helps avoid hours of frustrating tinkering attempting to adjust multitude of software “products” where CCSID conversions is possible.

Happens 99 times out of 100 when not working (CCSID 65535)

You cannot run IBM i PHP and interact with IBM i DB2, PGM, SRVPGM (xmlservice), when your IBM i machine is default ship setting of 65535. Change the default ship Zend Server setting and you will likely be up and running.

I run 65535 xmlservice on my V6R1 machine (among other machines)

DSPSYSVAL SYSVAL(QCCSID)
  Coded character set
    identifier . . . . . :   65535      1-65535

Change these files and restart everything (web, db2, xmlservice, tec.)

I. Apache: /www/zendsvr/conf/httpd.conf <– (ILE Apache side)

DefaultFsCCSID 37   ... or 280 (Italian) ... or so on ...
CGIJobCCSID 37      ... or 280 (Italian) ... or so on ...

This often fixes majority of web script issue

II. FastCGI: /www/zendsvr/conf/fastcgi.conf <– THIS file (PASE side)

CCSID=819 and LANG=C,
which works nearly anywhere (UNIX default).

Note:

  • This file must be ASCII 819, so careful with editor or will not work (EDTF especially).
  • CCSID=1208 can be a problem for PHP extensions, so most configurations use CCSID=819 and take specific script action to encode/decode 1208 data (see xmlservice hex/before/after)

III. User profile – DB2 connections

CHGUSRPRF USRPRF(IAM37) CCSID(37)

This fixes most DB2 CCSID client/server problems both 1-tier and 2-tier

– or (skip I. - III.)–

IV. change system ccsid for everything on the machine

CHGSYSVAL SYSVAL(QCCSID) VALUE(37)

Note:

  • some legacy applications may not work, should work but…, so check things out
  • 65535 means HEX (or binary), which also means no conversion and 65535 is essentially doomsday for most any application trying to work between PASE and DB2 (most any client and DB2).
client (ASCII 819) + server (EBCDIC 65535) = junk-you-can't-read-or-use = xmlservice fails

Now the rest of the story 65535 problem is common, so IBM i DB2 folks force a reasonable job CCSID for SOME remote connections (based on primary language generally), but NOT all connections (PASE and others), therefore observationally you may see some applications work (client access products) and others will not (PHP).

  1. Specific examples for XMLSERVICE
  • Command user ccsid override
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG4K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());

$ipc = "/tmp/packers"; // *** RIGHT HERE internalKey/IPC
$ctl = "*sbmjob";      // *** run state full

$clob = "<?xml version='1.0'?>\n";
$clob .= "<script>\n";
$clob .= "<cmd exec='rexx' hex='on' before='819/37' after='37/819'>";
$clob .= bin2hex("RTVJOBA USRLIBL(?) SYSLIBL(?)");
$clob .= "</cmd>\n";
$clob .= "</script>\n";
$clobIn = $clob;
$clobOut = "";

$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);

var_dump($clobOut);

$xmlobj = simplexml_load_string($clobOut);

if (!$xmlobj) die("Bad XML output");

$clobOut = pack("H*",(string)$xmlobj->cmd->hex);

var_dump($clobOut);

OUTPUT:
> php zzhexccsidcmd.php
string(527) "<?xml version='1.0'?>
<script>
<cmd exec='rexx' hex='on' before='819/37' after='37/819'><success>+++ success RTVJOBA USRLIBL(?) SYSLIBL(?)</success><hex>3C726F773E0A3C6461746120646573633D275553524C49424C273E5147504C202020202020205154454D5020202020202051444556454C4F5020202051424C445359532020202051424C44535953523C2F646174613E0A3C2F726F773E0A3C726F773E0A3C6461746120646573633D275359534C49424C273E5153595320202020202020515359533220202020202051484C5053595320202020515553525359533C2F646174613E0A3C2F726F773E0A</hex></cmd>
</script>"
string(176) "<row>
<data desc='USRLIBL'>QGPL       QTEMP      QDEVELOP   QBLDSYS    QBLDSYSR</data>
</row>
<row>
<data desc='SYSLIBL'>QSYS       QSYS2      QHLPSYS    QUSRSYS</data>
</row>
  • Program ccsid override
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) die("Bad connect: $database,$user");
$stmt = db2_prepare($conn, "call $libxmlservice.iPLUG512K(?,?,?,?)");
if (!$stmt) die("Bad prepare: ".db2_stmt_errormsg());

$ipc = "/tmp/packers"; // *** RIGHT HERE internalKey/IPC
$ctl = "*sbmjob";      // *** run state full

$clob = "<?xml version='1.0'?>\n";
$clob .= "<script>\n";
$clob .= "<pgm name='ZZSRV' lib='XMLSERVICE' func='ZZ200'>\n";
$clob .= "<parm io='both'>\n";
$clob .= "<data type='200A' hex='on' before='819/37' after='37/819'>";
$clob .= bin2hex("Hi there i am ok on return from xmlservice.");
$clob .= "</data>\n";
$clob .= "</parm>\n";
$clob .= "</pgm>\n";
$clob .= "</script>\n";
$clobIn = $clob;
$clobOut = "";

$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);

var_dump($clobOut);

$xmlobj = simplexml_load_string($clobOut);

if (!$xmlobj) die("Bad XML output");

$clobOut = pack("H*",(string)$xmlobj->pgm->parm->data);

var_dump($clobOut);


OUTPUT:
> php zzhexccsidpgm.php
string(273) "<?xml version='1.0'?>
<script>
<pgm name='ZZSRV' lib='XMLSERVICE' func='ZZ200'>
<parm io='both'>
<data type='200A' hex='on' before='819/37' after='37/819'>4869207468657265206920616D206F6B206F6E2072657475726E2066726F6D20786D6C736572766963652E</data>
</parm>
</pgm>
</script>"
string(43) "Hi there i am ok on return from xmlservice."
  1. Specific examples for New PHP Toolkit
  • CCSID override - PHP Toolkit/CW

Simple CCSIDs only require setting the CCSID via QCCSID or in Apache. The overrides directly below are intended for for languages with more complex needs such as Hebrew or Japanese, or when individual pieces of data are encoded differently (such as a combination of 819 and 1208).

The easiest way to try these CCSID settings is with three new settings in toolkit.ini:

advanced CCSID options. Use all three options together.
ccsidBefore = '819/37'
ccsidAfter = '37/819'
useHex = true

Uncomment the three settings and then adjust the ccsidBefore and ccsidAfter values according to your needs.

Another way to set these global CCSID settings is with the method setToolkitServiceParams(). In your code, after connecting with $conn::getInstance(* etc.), set the parameters with this statement:

$conn->setToolkitServiceParams(array('ccsidBefore'=>'819/37', 'ccsidAfter'=>'37/819', 'useHex'=>true));

This technique works identically to changing INI values, except that this coding technique can be re-done over and over with different settings before each program/command call.

These “global” CCSID techniques work with both the new API and the CW, and will convert not only data/commands and command output, but the names of programs, libraries, and functions. You may notice that your data will be converted to hex inside the toolkit and then converted back to readable text by the toolkit.

For more fine-grained control over parameter data–that is, the ability to use a different CCSID conversion for each parameter, if desired–chain several new methods to AddParameterChar() like so: (new API only–not in CW):

$param[] = $conn->AddParameterChar('both', 10,'CODE', 'CODE', $code)
                ->setParamCcsidBefore('819/37')
                ->setParamCcsidAfter('37/819')
                ->setParamUseHex(true);

These parameters can also be passed as AddParameterChar() function parameters directly but it’s easier to use the setParam… methods above.

Note:

These advanced CCSID settings do not affect some of the handmade API calls in the CW such as getting object lists. Helping those may be a future enhancement.

If you wish to see how XMLSERVICE implements these overrides, see the following URL, under the heading: “CCSID user override - xmlservice options (hex/before/after)”.

Toolkit go faster

Goto Main Page

Before you start …

This page is about running faster using persistent and private pooled connections workload balancing techniques. This is not a discussion about using QTEMP or *LDA in called RPG programs across browser clicks, that is a different topic entirely.

New version Zend Server include ZRAY

Server set up asks if Developer or Production. If Developer, Z-Ray is on by default, but easy to turn off. If Production, Z-Ray is off by default, but easy to turn on. Z-Ray is recommended to be off in Production for performance and security reasons. It can be set up in secured mode in production to only be used on pages deliberately accessed by a developer.

Toolkit connections performance

There are many ways to workload balance PHP Toolkit connections:

  • slower … public “stateless” job - no ‘InternalKey’ (default)
$ToolkitServiceObj->setToolkitServiceParams( array('stateless'=>true));
  • faster … private “state full ” single job - $user=”SALLY” connected to single xmlservice job
// php:db2_connect <- XML IN/OUT -> QSQSRVR:XMLSERVICE <- "/tmp/$user" -> XTOOLKIT:XMLSERVICE
$ToolkitServiceObj->setToolkitServiceParams(array('InternalKey'=>"/tmp/$user"))
  • fastest … private “state full” pool jobs - $user=”SALLY”.rand(1,10) connected to 10 random xmlservice jobs
// php:db2_connect :<- XML IN/OUT -> QSQSRVR:XMLSERVICE <- "/tmp/$user1"  -> XTOOLKIT:XMLSERVICE(1)
// rand pick server:
//                 :<- XML IN/OUT -> QSQSRVR:XMLSERVICE <- "/tmp/$user10" -> XTOOLKIT:XMLSERVICE(10)
$ToolkitServiceObj->setToolkitServiceParams(array('InternalKey'=>"/tmp/$user".rand(1,10))

The following is a relative performance guideline:

  • slower … public “stateless” job … stateless connection (safe default)
<?php
// job 1 (client)                      job 2 (server)
// any php-cgi job                     attach QSQSRVR call XMSLERVICE
// ------------------                  ------------------------------
// php:db2_(p)connect <- XML IN/OUT -> QSQSRVR:XMLSERVICE

$extension='ibm_db2';
try { $ToolkitServiceObj = ToolkitService::getInstance($db, $user, $pass, $extension); }
catch (Exception $e) { echo  $e->getMessage(), "\n"; exit(); }
$options = array('stateless'=>true,'plugSize'=>'4K');
$ToolkitServiceObj->setToolkitServiceParams($options);
$ToolkitServiceObj->disconnect();
  • slower … uses db2_connect / odbc_connect (full open/close of QSQSRVR job)

  • slower … starts/stops xmlservice within QSQSRVR job each script request

  • What to watch for …

    not much, this is a simple full start/stop model (slow, but safe mostly)

  • faster … private pool jobs … state full connection (private)

<?php
// job 1 (client)                      job 2 (proxy)                           job 3..13 (10 servers)
// any php-cgi job                     QSQSRVR passthru                        XTOOLKIT(s) ready (always)
// ------------------                  ------------------                      --------------------------
// php:db2_(p)connect:<- XML IN/OUT -> QSQSRVR:XMLSERVICE <- "/tmp/$user1"  -> XTOOLKIT:XMLSERVICE(1)
//                   :<- XML IN/OUT -> QSQSRVR:XMLSERVICE <- "/tmp/$user2"  -> XTOOLKIT:XMLSERVICE(2)
// rand pick a server:
//                   :<- XML IN/OUT -> QSQSRVR:XMLSERVICE <- "/tmp/$user10" -> XTOOLKIT:XMLSERVICE(10)

$extension='ibm_db2';
try { $ToolkitServiceObj = ToolkitService::getInstance($db, $user, $pass, $extension); }
catch (Exception $e) { echo  $e->getMessage(), "\n"; exit(); }
$maxpool = 10; // 10 jobs good enough to handle my machine needs
$internalKey = '/tmp/packers'.rand(1,$maxpool);
$ToolkitServiceObj->setToolkitServiceParams(array('InternalKey'=>$internalKey));
/* Do not use the disconnect() function for "state full" connection */
/* NEVER EVER USE THIS ... $ToolkitServiceObj->disconnect();        */
/* Why? *immed kill of job, not nice or sync, just kill             */
  • slower … uses db2_connect / odbc_connect (full open/close of QSQSRVR job)

  • fastest … starts another xmlservice /tmp/packers1-to-10 beyond QSQSRVR job (XTOOLKIT PGM-XMLSERVICE), and jobs 1-10 use over and over and over … until killed by $ToolkitServiceObj->disconnect() or by IBM i operator

  • What to watch for …

    Security 1: profile FRED can not attach to profile SALLY XMLSERVICE jobs (SALLY owns /tmp/packers1-10, FRED will have to make his own jobs /tmp/bears1-10)

    Co-operate with web site: develop procedures start/stop xmlservice when doing system maintenance (kill xmlservice jobs, etc.)

    A live job: QTEMP/*LDA are re-used, therefore your called applications must be ready to handle/clear

  • fastest … private pool jobs … add persistent db2 connections (db2_pconnect)

<?php
// job 1 (client)                      job 2 (proxy)                           job 3..13 (10 servers)
// any php-cgi job                     QSQSRVR passthru                        XTOOLKIT(s) ready (always)
// ------------------                  ------------------                      --------------------------
// php:db2_(p)connect:<- XML IN/OUT -> QSQSRVR:XMLSERVICE <- "/tmp/$user1"  -> XTOOLKIT:XMLSERVICE(1)
//                   :<- XML IN/OUT -> QSQSRVR:XMLSERVICE <- "/tmp/$user2"  -> XTOOLKIT:XMLSERVICE(2)
// rand pick a server:
//                   :<- XML IN/OUT -> QSQSRVR:XMLSERVICE <- "/tmp/$user10" -> XTOOLKIT:XMLSERVICE(10)

require_once("ToolkitService.php");
$i5persistentconnect = true;
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) echo "Bad connect: $conn,$database,$user,perm=$i5persistentconnect";
try { $ToolkitServiceObj = ToolkitService::getInstance($conn); }
catch (Exception $e) { die($e->getMessage()); }
$maxpool = 10; // 10 jobs good enough to handle my machine needs
$ToolkitServiceObj->setToolkitServiceParams(array('InternalKey'=>'/tmp/packers'.rand(1,$maxpool),'plug'=>'iPLUG32K'));
  • fastest … uses db2_pconnect / odbc_pconnect (persistent QSQSRVR job stays alive “forever”)

  • fastest … starts another xmlservice /tmp/packers1-to-10 beyond QSQSRVR job (XTOOLKIT PGM-XMLSERVICE), and jobs 1-10 use over and over and over … until killed by $ToolkitServiceObj->disconnect() or by IBM i operator

  • What to watch for …

    Security 1: profile FRED can not attach to profile SALLY XMLSERVICE jobs (SALLY owns /tmp/packers1-10, FRED will have to make his own jobs /tmp/bears1-10)

    Security 2: profile FRED owns a db2_pconnect(tion), and SALLY owns a db2_pconnect(ion), XMLSERVICE connect InternalKey profile must match (db2_pconnect(“SALLY”) owns /tmp/packers1-10, db2_pconnect(“FRED”) owns /tmp/bears1-10)

    Co-operate with web site: develop procedures start/stop xmlservice when doing system maintenance (kill xmlservice jobs, etc.)

    A live job: QTEMP/*LDA are re-used, therefore your called applications must be ready to handle/clear

Toolkit operations performance

Always use PgmCall API for speed including data area, job attributes, etc. (V6+ also call CL and OPM *PGM with PgmCall), most command functions will run significantly slower.

  • slower … PASE sh utilities (system wrkactjob, ls, ps, etc.)
$ToolkitServiceObj->CLInteractiveCommand
  • slightly faster … CMDS that return data (RTVJOBA, etc.)
$ToolkitServiceObj->CLCommandWithOutput
  • faster … CMDS that do not return data (ADDLIBLE, etc.)
$ToolkitServiceObj->CLCommand
  • fastest … calling PGMs/SRVPGMs (RPG, CLP, Cobol, System API, etc.)
$ToolkitServiceObj->PgmCall

Toolkit plug size performance

Setting plug size to match your data size can offer increased performance.

  • slower … 15 MB plug size (max)
$ToolkitServiceObj->setToolkitServiceParams(array('InternalKey'=>'/tmp/packers'.rand(1,$maxpool),'plugSize' => '15M'));
  • faster … 512K plug size (default)
$ToolkitServiceObj->setToolkitServiceParams(array('InternalKey'=>'/tmp/packers'.rand(1,$maxpool),'plugSize'=>'512K'));
  • fastest … 4K plug size (min)
$ToolkitServiceObj->setToolkitServiceParams(array('InternalKey'=>'/tmp/packers'.rand(1,$maxpool),'plugSize'=>'4K'));

Why a plug size at all?

DB2 connections are safe reliable transport for XML documents between client (PHP) and server (XMLSERVICE), but DB2 forces you to declare IN/OUT parameter size of any call procedure, XMLSERVICE download includes a few different stored procedure sizes (iPLUG4k .. iPLUG15M), so your script needs to choose the IN/OUT size that fits your data.

XMLSERVICE Performance

Goto Main Page

Performance at a glance

The following tests are full browser simulations (Apache ab tool), not PHP loop tests

  • Apache ab web site stress tests are performed from the 5250 command line (call qp2term) or ssh myibmi using PASE (see Apache ab link install instructions)
    • > ab -t 15 -c 10 http://lp0264d/level/xxtoolkit_new.php
      • You can run stress tests from 2-tier Linux/Windows using Apache ab tool, but i am using Apchae ab from PASE.
      • Apache ab tool is not perfect, but if you use relatively “sane” number of browsers like -c 10 it will work.
      • Apache ab test is designed to drive CPU to 100% (a good thing), so don’t panic about CPU
Connection Sample (ab tool)
Apache <H1>Hello world</H1> …………………………… Requests per second: 767.87
PHP <?php echo "Hello world"; ?> ……………………. Requests per second: 560.05
ibm_db2

$conn = db2_connect($database,$user,$password); ……. Requests per second: 64.54

$conn = db2_pconnect($database,$user,$password); …… Requests per second: 504.04

$ipc     = "";
$ctl     = "*justproc";          // *justproc (no progam call)
$clobIn  = "<?xml version='1.0'?>";
$clobOut = "";
$stmt = db2_prepare($conn, "call XMLSERVICE.iPLUG4K(?,?,?,?)");
$ret  = db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret  = db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret  = db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret  = db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret  = db2_execute($stmt);
echo "success";
XMLSERVICE

$ipc = ''; $ctl = '*here'; ………………………. Requests per second: 69.58

$ipc = '/tmp/packers'; $ctl = '*sbmjob'; …………… Requests per second: 320.72

<?php
require_once('connection2.inc');
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) echo "Bad connect: $conn,$database,$user,perm=$i5persistentconnect";
$ipc  = '/tmp/packers';    // *here no need ipc
$ctl  = "*sbmjob";         // *here for no additional private job
// $ipc  = '';
// $ctl  = '*here';
$clobIn = "<?xml version='1.0'?>
<pgm name='ZZCALL' lib='$libxmlservice'>
 <parm  io='both'>
   <data type='1A'>a</data>
 </parm>
 <parm  io='both'>
   <data type='1A'>b</data>
 </parm>
 <parm  io='both'>
   <data type='7p4'>11.1111</data>
 </parm>
 <parm  io='both'>
   <data type='12p2'>222.22</data>
 </parm>
 <parm  io='both'>
  <ds>
   <data type='1A'>x</data>
   <data type='1A'>y</data>
   <data type='7p4'>66.6666</data>
   <data type='12p2'>77777.77</data>
  </ds>
 </parm>
 <return>
  <data type='10i0'>0</data>
 </return>
</pgm>";
$clobOut = "";
$stmt = db2_prepare($conn, "call XMLSERVICE.iPLUG4K(?,?,?,?)");
$ret=db2_bind_param($stmt, 1, "ipc", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 2, "ctl", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 3, "clobIn", DB2_PARAM_IN);
$ret=db2_bind_param($stmt, 4, "clobOut", DB2_PARAM_OUT);
$ret=db2_execute($stmt);
// var_dump($clobOut);
if (strpos($clobOut,"4444444444.44")>0) echo "success";
else echo "fail";
?>
Toolkit

setToolkitServiceParams(array('stateless'=>true)); ….. Requests per second: 62.97

<?php
require_once('connection2.inc');
require_once("ToolkitService.php");
if ($i5persistentconnect) $conn = db2_pconnect($database,$user,$password);
else $conn = db2_connect($database,$user,$password);
if (!$conn) echo "Bad connect: $conn,$database,$user,perm=$i5persistentconnect";
try { $ToolkitServiceObj = ToolkitService::getInstance($conn); }
catch (Exception $e) { die($e->getMessage()); }
// $ToolkitServiceObj->setToolkitServiceParams(array('stateless'=>true,
           'plug'=>'iPLUG32K'));
ToolkitServiceObj->setToolkitServiceParams(array('InternalKey'=>'/tmp/packers',
            'plug'=>'iPLUG32K'));
$param[] = $ToolkitServiceObj->AddParameterChar   ('both',  1,  'INCHARA', 'var1', 'Y');
$param[] = $ToolkitServiceObj->AddParameterChar   ('both',  1,  'INCHARB', 'var2', 'Z');
$param[] = $ToolkitServiceObj->AddParameterPackDec('both',  7,4,'INDEC1',  'var3',
            '001.0001');
$param[] = $ToolkitServiceObj->AddParameterPackDec('both', 12,2,'INDEC2',  'var4',
            '0000000003.04');
   $ds[] = $ToolkitServiceObj->AddParameterChar   ('both',  1,  'DSCHARA', 'ds1',  'A');
   $ds[] = $ToolkitServiceObj->AddParameterChar   ('both',  1,  'DSCHARB', 'ds2',  'B');
   $ds[] = $ToolkitServiceObj->AddParameterPackDec('both',  7,4,'DSDEC1',  'ds3',
             '005.0007');
   $ds[] = $ToolkitServiceObj->AddParameterPackDec('both', 12,2,'DSDEC1',  'ds4',
             '0000000006.08');
$param[] = $ToolkitServiceObj->AddDataStruct($ds);
$clobOut  = $ToolkitServiceObj->PgmCall('ZZCALL', $libxmlservice, $param, null, null);
// var_dump($clobOut);
$value = "what is ...".$clobOut["io_param"]["ds4"];
if (strpos($value,"4444444444.44")>-1) echo "success";
else echo "fail";
?>

How many users?

Apache ab tool is designed to “exercise” CPU/Apache engine as fast as possible, therefore ‘ab tool’ requests/second multiply by a factor of 5-10x to estimate ‘user capacity’ assuming humans actually read/scan each browser page (~ 5-10 seconds per page). So, on this machine using just ibm_db2 without toolkit, the following is about as fast as any given connection can run using only ONE job.

Drive the CPU to 100% capacity, then estimate actual users supported to drive the same CPU 100%.
>> ab -t 15 -c 15 http://lp0264d/ibm_db2_fetch.php?pool
      483 requests/second..............483 users/second (varies widely per application base line)
    28,980 requests/minute..............600 users/minute (relatively constant user capacity ibm_db2 scripts persistent connections)
1,738,800 requests/hour.............36,000 users/hour   (relatively constant user capacity ibm_db2 scripts persistent connections)
41,731,200 requests/day (24 hours)..864,000 users/day    (relatively constant user capacity ibm_db2 scripts persistent connections)

Things to consider:

  • Who am i testing with ab tool?
    • lighting fast ab tool “no delay” push CPU 100% (superman users faster than a speeding bullet … a machine)
    • normal thinking users “10 second delay user actions” to push CPU 100% (average Joe … a human)
    • your application complexity may go far beyond simple ibm_db2_fetch.php above, but most any web ‘load runner’ tool (like ‘ab tool’) or worse php script ‘loop call test’ requires human action calculation to understand user capacity, so don’t believe blog people until you actually run your application specific performance test.

If you want to run Apache ab tool on PASE (IBM i) …

  • ab — Apache ab tool from old Zend Core (simulate browsers)

XMLSERVICE still evolving

Toolkit/XMLSERVICE performance will evolve over time, we understand performance is important and we intend push data through faster (perhaps much faster).

  • What runs fast?
    • calling PGMs/SRVPGMs (RPG, CLP, Cobol, System API, etc.)
      • loading your program is relatively slow so keep process alive (see below)
      • use persistent connections (db2_pconnect/i5_pconnect)
      • use IPC state full XMLSERVICE (‘InternalKey’=>’/tmp/packers’)
      • turn off PHP debug, logs, traces toolkit all ini files
  • What runs slower?
    • CMDs, especially CMDS that return data (RTVJOBA, etc.)
      • CMDs return data use REXX, which is slow first time
      • CMDs that do NOT return data, will run fairly quickly
      • call CLP using the program interface (not command interface)
      • use IPC state full XMLSERVICE (‘InternalKey’=>’/tmp/packers’)
      • turn off PHP debug, logs, traces toolkit all ini files
  • What runs slowest?
    • PASE sh utilities (system wrkactjob, ls, ps, etc.)
      • not much can be done because most time due to fork of another job (Unix/PASE style)
      • what little can be done is mostly same all call types
      • use IPC state full XMLSERVICE (‘InternalKey’=>’/tmp/packers’)
      • turn off PHP debug, logs, traces toolkit all ini files

XMLSERVICE Errors

Goto Main Page

Common errors

<errnoile>3401</errnoile>
<errnoilemsg><![CDATA[Permission denied.]]></errnoilemsg>
<errnoxml>1301011</errnoxml>
<xmlerrmsg><![CDATA[IPC shmat fail 1]]></xmlerrmsg>
<xmlhint><![CDATA[/tmp/packers3]]></xmlhint>
</error>

errnoile 3401 – usually means another process with a different user profile is using IPC (/tmp/packers3)

<errnoile>3021</errnoile>
<errnoxml>1301009</errnoxml>
<xmlerrmsg>IPC getshm fail</xmlerrmsg>
<xmlhint><![CDATA[/tmp/ ]]></xmlhint>

‘Hung’ semaphores/shared memory associated with a user never suppose to happen, but have seen in rare occasion. The following commands can be used to remove “hung” semaphores/shared memory associated with a user assuming you have appropriate authority to run like SECOFR, etc. (Ranger welcomes you to Unix geek-ville).

Example:

grep -i qtm means ipcrm for (QTM)HHTTP ...

endTCPSVR SERVER(*HTTP) INSTANCE(ZENDSVR)                               -- suggest end web server

call qp2term
> ipcs | grep -i qtm | awk '{print "ipcrm -" tolower($1) " "$2}'        -- show action, but NOT do  action
> ipcs | grep -i qtm | awk '{print "ipcrm -" tolower($1) " "$2}' | sh   -- remove semaphores/shared memory

strTCPSVR SERVER(*HTTP) INSTANCE(ZENDSVR)                               -- suggest start web server
<errnoxml>1000005</errnoxml>
<xmlerrmsg>PASE resolve failed</xmlerrmsg>
<xmlhint><![CDATA[MYPGM]]></xmlhint>

The program you tried to call, shown here as “MYPGM” (in the CDATA tag), was not found. Make sure you specified the library and program correctly, including upper or lower case (usually upper case).

<errnoxml>1480002</errnoxml>
<xmlerrmsg>XMLCGI invalid</xmlerrmsg>
<xmlhint>*NONE</xmlhint>

*NONE requires a special compile of the RPG source and is NOT enabled in production versions of the toolkit by default. It is most useful for demos with custom security like this site, if you try on your machine you will likely get 1480002 error XMLCGI. You can find details in plugerr_h.

ILE errno

Errno Values for UNIX-Type Functions

Programs using the UNIX(R)-type functions may receive error information as errno values. The possible values returned are listed here in ascending errno value sequence.

Name              Value   Text
EDOM              3001    A domain error occurred in a math function.
ERANGE            3002    A range error occurred.
ETRUNC    3003    Data was truncated on an input, output, or update operation.
ENOTOPEN        3004      File is not open.
ENOTREAD        3005      File is not opened for read operations.
EIO               3006    Input/output error.
ENODEV            3007    No such device.
ERECIO            3008    Cannot get single character for files opened for record I/O.
ENOTWRITE       3009      File is not opened for write operations.
ESTDIN            3010    The stdin stream cannot be opened.
ESTDOUT         3011      The stdout stream cannot be opened.
ESTDERR         3012      The stderr stream cannot be opened.
EBADSEEK        3013      The positioning parameter in fseek is not correct.
EBADNAME        3014      The object name specified is not correct.
EBADMODE        3015      The type variable specified on the open function is not correct.
EBADPOS         3017      The position specifier is not correct.
ENOPOS            3018    There is no record at the specified position.
ENUMMBRS        3019      Attempted to use ftell on multiple members.
ENUMRECS        3020      The current record position is too long for ftell.
EINVAL            3021    The value specified for the argument is not correct.
EBADFUNC        3022      Function parameter in the signal function is not set.
ENOENT            3025    No such path or directory.
ENOREC    3026    Record is not found.
EPERM             3027    The operation is not permitted.
EBADDATA        3028      Message data is not valid.
EBUSY             3029    Resource busy.
EBADOPT         3040      Option specified is not valid.
ENOTUPD         3041      File is not opened for update operations.
ENOTDLT         3042      File is not opened for delete operations.
EPAD              3043    The number of characters written is shorter than the expected record length.
EBADKEYLN       3044      A length that was not valid was specified for the key.
EPUTANDGET      3080      A read operation should not immediately follow a write operation.
EGETANDPUT      3081      A write operation should not immediately follow a read operation.
EIOERROR        3101      A nonrecoverable I/O error occurred.
EIORECERR       3102      A recoverable I/O error occurred.
EACCES            3401    Permission denied.
ENOTDIR         3403      Not a directory.
ENOSPC            3404    No space is available.
EXDEV             3405    Improper link.
EAGAIN            3406    Operation would have caused the process to be suspended.
EWOULDBLOCK     3406      Operation would have caused the process to be suspended.
EINTR             3407    Interrupted function call.
EFAULT            3408    The address used for an argument was not correct.
ETIME             3409    Operation timed out.
ENXIO             3415    No such device or address.
EAPAR             3418    Possible APAR condition or hardware failure.
ERECURSE        3419      Recursive attempt rejected.
EADDRINUSE      3420      Address already in use.
EADDRNOTAVAIL   3421      Address is not available.
EAFNOSUPPORT    3422      The type of socket is not supported in this protocol family.
EALREADY        3423      Operation is already in progress.
ECONNABORTED    3424      Connection ended abnormally.
ECONNREFUSED    3425      A remote host refused an attempted connect operation.
ECONNRESET      3426      A connection with a remote socket was reset by that socket.
EDESTADDRREQ    3427      Operation requires destination address.
EHOSTDOWN       3428      A remote host is not available.
EHOSTUNREACH    3429      A route to the remote host is not available.
EINPROGRESS     3430      Operation in progress.
EISCONN         3431      A connection has already been established.
EMSGSIZE        3432      Message size is out of range.
ENETDOWN        3433      The network currently is not available.
ENETRESET       3434      A socket is connected to a host that is no longer available.
ENETUNREACH     3435      Cannot reach the destination network.
ENOBUFS         3436      There is not enough buffer space for the requested operation.
ENOPROTOOPT     3437      The protocol does not support the specified option.
ENOTCONN        3438      Requested operation requires a connection.
ENOTSOCK        3439      The specified descriptor does not reference a socket.
ENOTSUP         3440      Operation is not supported.
EOPNOTSUPP      3440      Operation is not supported.
EPFNOSUPPORT    3441      The socket protocol family is not supported.
EPROTONOSUPPORT 3442      No protocol of the specified type and domain exists.
EPROTOTYPE      3443      The socket type or protocols are not compatible.
ERCVDERR        3444      An error indication was sent by the peer program.
ESHUTDOWN       3445      Cannot send data after a shutdown.
ESOCKTNOSUPPORT 3446      The specified socket type is not supported.
ETIMEDOUT       3447      A remote host did not respond within the timeout period.
EUNATCH         3448      The protocol required to support the specified address family is not available at this time.
EBADF             3450    Descriptor is not valid.
EMFILE            3452    Too many open files for this process.
ENFILE            3453    Too many open files in the system.
EPIPE             3455    Broken pipe.
ECANCEL         3456      Operation cancelled.
EEXIST            3457    File exists.
EDEADLK         3459      Resource deadlock avoided.
ENOMEM            3460    Storage allocation request failed.
EOWNERTERM      3462      The synchronization object no longer exists because the owner is no longer running.
EDESTROYED      3463      The synchronization object was destroyed, or the object no longer exists.
ETERM             3464    Operation was terminated.
ENOENT1         3465      No such file or directory.
ENOEQFLOG       3466      Object is already linked to a dead directory.
EEMPTYDIR       3467      Directory is empty.
EMLINK            3468    Maximum link count for a file was exceeded.
ESPIPE            3469    Seek request is not supported for object.
ENOSYS            3470    Function not implemented.
EISDIR            3471    Specified target is a directory.
EROFS             3472    Read-only file system.
EUNKNOWN        3474      Unknown system state.
EITERBAD        3475      Iterator is not valid.
EITERSTE        3476      Iterator is in wrong state for operation.
EHRICLSBAD      3477      HRI class is not valid.
EHRICLBAD       3478      HRI subclass is not valid.
EHRITYPBAD      3479      HRI type is not valid.
ENOTAPPL        3480      Data requested is not applicable.
EHRIREQTYP      3481      HRI request type is not valid.
EHRINAMEBAD     3482      HRI resource name is not valid.
EDAMAGE         3484      A damaged object was encountered.
ELOOP             3485    A loop exists in the symbolic links.
ENAMETOOLONG    3486      A path name is too long.
ENOLCK            3487    No locks are available.
ENOTEMPTY       3488      Directory is not empty.
ENOSYSRSC       3489      System resources are not available.
ECONVERT        3490      Conversion error.
E2BIG             3491    Argument list is too long.
EILSEQ            3492    Conversion stopped due to input character that does not belong to the input codeset.
ETYPE             3493    Object type mismatch.
EBADDIR         3494      Attempted to reference a directory that was not found or was destroyed.
EBADOBJ         3495      Attempted to reference an object that was not found, was destroyed, or was damaged.
EIDXINVAL       3496      Data space index used as a directory is not valid.
ESOFTDAMAGE     3497      Object has soft damage.
ENOTENROLL      3498      User is not enrolled in system distribution directory.
EOFFLINE        3499      Object is suspended.
EROOBJ            3500    Object is a read-only object.
EEAHDDSI        3501      Hard damage on extended attribute data space index.
EEASDDSI        3502      Soft damage on extended attribute data space index.
EEAHDDS         3503      Hard damage on extended attribute data space.
EEASDDS         3504      Soft damage on extended attribute data space.
EEADUPRC        3505      Duplicate extended attribute record.
ELOCKED         3506      Area being read from or written to is locked.
EFBIG             3507    Object too large.
EIDRM             3509    The semaphore, shared memory, or message queue identifier is removed from the system.
ENOMSG            3510    The queue does not contain a message of the desired type and (msgflg logically ANDed with IPC_NOWAIT).
EFILECVT        3511      File ID conversion of a directory failed.
EBADFID         3512      A file ID could not be assigned when linking an object to a directory.
ESTALE            3513    File handle was rejected by server.
ESRCH             3515    No such process.
ENOTSIGINIT     3516      Process is not enabled for signals.
ECHILD            3517    No child process.
EBADH             3520    Handle is not valid.
ETOOMANYREFS    3523      The operation would have exceeded the maximum number of references allowed for a descriptor.
ENOTSAFE        3524      Function is not allowed.
EOVERFLOW       3525      Object is too large to process.
EJRNDAMAGE      3526      Journal is damaged.
EJRNINACTIVE    3527      Journal is inactive.
EJRNRCVSPC      3528      Journal space or system storage error.
EJRNRMT         3529      Journal is remote.
ENEWJRNRCV      3530      New journal receiver is needed.
ENEWJRN         3531      New journal is needed.
EJOURNALED      3532      Object already journaled.
EJRNENTTOOLONG  3533      Entry is too large to send.
EDATALINK       3534      Object is a datalink object.
ENOTAVAIL       3535      IASP is not available.
ENOTTY            3536    I/O control operation is not appropriate.
EFBIG2            3540    Attempt to write or truncate file past its sort file size limit.
ETXTBSY         3543      Text file busy.
EASPGRPNOTSET   3544      ASP group not set for thread.
ERESTART        3545      A system call was interrupted and may be restarted.
ESCANFAILURE    3546      An object has been marked as a scan failure due to processing by an exit program associated with the scan-related integrated file system exit points.

XMLSERVICE FAQ

Goto Main Page

Tip

Some webserver products take issue with <script> in XML documents. If you find chunks missing in your XML, try <myscript>

<script>   change  <myscript>
:           to     :
</script>  this    </myscript>

XMLSERVICE is hanging right out of box (because CCSID 65535)

Q: XMLSERVICE is returning junk, how can i fix?
Q: XMLSERVICE is not working at all and IPC /tmp/xxx dir not created, how can i fix?
Q: XMLSERVICE is hanging?
A: You need to change Apache settings.

The following steps lead to a successful test::

end current running jobs:
ENDTCPSVR SERVER(*HTTP) HTTPSVR(ZENDSVR)
ENDPJ SBS(QSYSWRK) PGM(QSQSRVR) OPTION(*IMMED)

check the system CCSID value is 65535:
DSPSYSVAL SYSVAL(QCCSID)
Coded character set
  identifier . . . . . :   65535      1-65535

edit configuration:
/www/zendsvr/conf/fastcgi.conf (optional from editor):
SetEnv="LANG=C"

/www/zendsvr/conf/httpd.conf (web admin GUI port 2001 - ZENDSVR):
DefaultFsCCSID 37  ... or 208 (Italian) ... or so on ...
CGIJobCCSID 37     ... or 208 (Italian) ... or so on ...

restart jobs:
STRPJ SBS(QSYSWRK) PGM(QSQSRVR)
STRTCPSVR SERVER(*HTTP) HTTPSVR(ZENDSVR)


wrkactjob
If you see ANY XMLSERVICE jobs, kill them before retry.

This will allow XMLSERVICE to work (IPC was junk not /tmp/whatever).
This should fix "junk" from ibm_db2 on i (odbc, pdo_ibm, etc.).

php-cli command line issues (CCSID 65535) …

The easy way to fix this is to simply CHGUSRPRF and specify a valid CCSID like 37, then as you sign-on (ssh, call qp2term) you will not be stuck with nasty 65535 problems.

And now i can run my test cases (ssh->tcsh)

>setenv PATH /usr/local/zendsvr/bin:$PATH
>setenv LIBPATH /usr/local/zendsvr/lib
>cd /mytests
>pear run-tests *.phpt
>php test.php

XMLSERVICE DBCS (CCSID 5035)

Q: Did anyone try DBCS with XMLSERVICE?
A: Yes. Japanese 5035 appears to work.
The following steps lead to a successful test:

end current running jobs:
ENDTCPSVR SERVER(*HTTP) HTTPSVR(ZENDSVR)
ENDPJ SBS(QSYSWRK) PGM(QSQSRVR) OPTION(*IMMED)

change the system CCSID value:
CHGSYSVAL SYSVAL(QCCSID) VALUE(5035)

edit configuration:
/www/zendsvr/conf/fastcgi.conf (editor:
SetEnv="CCSID=1208" SetEnv="LANG=C"

/www/zendsvr/conf/httpd.conf (web admin GUI port 2001 - ZENDSVR):
DefaultFsCCSID 5035
CGIJobCCSID 5035

restart jobs:
STRPJ SBS(QSYSWRK) PGM(QSQSRVR)
STRTCPSVR SERVER(*HTTP) HTTPSVR(ZENDSVR)

Note: If using ODBC will likely have to start/stop prestart jobs as well.

XMLSERVICE multi-CCSID or problems?

Using default PHP toolkit DB2 clob interface (iPLUGxxx/iPLUGRxxx), ccsid conversion occurs naturally as DB2 client/server and you will not have to code before/after, but method is available if you have a specific concern or you have scripts returning many different languages.

Example:

  <data type='200A' before='819/424' after='424/819'>bin2hex('Hebrew_ascii_raw_chars')</data>
  <data type='200A' before='819/1098' after='1098/819'>bin2hex('Farsi_ascii_raw_chars')</data>
  <data type='200A' before='819/880' after='880/819'>bin2hex('Russia_ascii_raw_chars')</data>
  <data type='200A' before='819/280' after='280/819'>bin2hex('Italy_ascii_raw_chars')</data>
  <data type='200A' before='819/273' after='273/819'>bin2hex('Germany_ascii_raw_chars')</data>
  <data type='200A' before='819/1088' after='1088/819'>bin2hex('Korea_ascii_raw_chars')</data>
  <data type='200A' before='1208/13488' after='13488/1208'>bin2hex('Japan_ascii_raw_chars')</data>
  <data type='200A' before='1208/13488' after='13488/1208'>bin2hex('China_ascii_raw_chars')</data>
where:
  before    - XMLSERVICE convert CCSID before ILE program call
  after     - XMLSERVICE convert CCSID after ILE program call for client return
  bin2hex() - script hex string unaltered ascii image (also returned hex string avoid any conversion)
  pack()    - script uses pack('H*',"xml_hex_back") function in PHP program for ascii characters
Note:
  Up to four conversions can take place for the truly diabolical ccsid issues
  <data type='A' before='cc1/cc2/cc3/cc4' after='cc4/cc3/cc2/cc1'>bin2hex('wild_ascii_raw_chars')</data>
  flow:
  -> PHP client bin2hex('wild_ascii_raw_chars')
  -> xmlservice hex2bin back to 'wild_ascii_raw_chars'
  -> xmlservice convert cc1->cc2->cc3->cc4 (before)
  -> xmlservice make ILE call
  -> xmlservice convert cc4->cc3->cc2->cc1 (after)
  -> xmlservice tohex "xml_hex_back"
  -> PHP client $chars = pack('H*',"xml_hex_back")

Can i use CDATA for XML special characters?

Many common XMLSERVICE tags already support a form of CDATA

<data><![CDATA[<i am tony>]]></data>
<query><![CDATA[select * from animal where ID < 5 and weight > 10.0]]></query>
<prepare><![CDATA[select * from animal where ID < ? and weight > ?]]></prepare>

BUT there are restrictions for speed of parsing (i think reasonable)

  • not allowed to put reserved words in cdata - NO <query><![CDATA[</query>]]></query>
  • no binary data (character data only) - <query><![CDATA[binary stuff]]></query>
  • there may be other restrictions because i don’t know everything about CDATA “abuse” in XML.

How do i kill/end XMLSERVICE jobs?

XMLSERVICE jobs stay active until killed with CTL option *immed. You need only know the ipc name (/tmp/fred01, /tmp/sally43, etc.), and have appropriate profile abilities to issue the XML/CTL kill to a running XMLSERVICE job.

Example kill XMLSERVICE for ‘/tmp/rangerusr’:

$ipc='/tmp/rangerusr';
$ctlKill="*immed";
$clobInKill = '<?xml version="1.0"?>';
$sql = "call $libxmlservice.iPLUGR4K('$ipc','$ctlKill','$clobInKill')";
$ret=db2_exec($conn,$sql);

Can i run “stateless” XMLSERVICE jobs (*here)?

XMLSERVICE can also run in the stored procedure (or web) job by using the ctl=’*here’ option. When you run in *here mode you do NOT need an IPC (ipc is ignored). XMLSERVICE is just another program in the job in *here mode, so when the job ends so does XMLSERVICE.

Example XMLSERVICE running in stored procedure job:

$ipc='not need ipc it is ignored';
$ctl="*here";
$clobIn = '<?xml version="1.0"?>
           <script>do stuff</script>';
$sql = "call $libxmlservice.iPLUGR512K('$ipc','$ctl','$clobIn')";
$ret=db2_exec($conn,$sql);

Note: Performance is generally slower when running in this mode, because XMLSERVICE has to start from scratch each time. There are varying degrees of overall XMLSERVICE performance using *here (“stateless”) depending on application interface supporting persistent/non-persistent/private connections (ie., db2_connect vs. db2_pconnect).

Warning: It should be noted XMLSERVICE “private” connection with ipc (ipc=/tmp/fred01, ipc=/tmp/fred02, ipc=/tmp/sally43) is most often the best high performance fit for calling “real” RPG programs because most RPG programs actually do something other than “hello world” with multiple files open and lot’s of state you do not want to restart each time called (much better to dedicate a XMLSERVICE job to the task). So, while *here is an easy answer for operator style process management (older toolkit options), and works ok for small “calculate this” SRVPGMs, you will likely find *here does not work in a practical world where business tasks are much more complex (ie. exactly why XMLSERVICE is optimized for using a IPC connection).

What is IPC??

IPC is traditional computer science abbreviation for InterProcess Communication (IPC), which in the case of XMLSERVICE is simply a unique directory name /tmp/mydir01 used for machine-wide unique ‘key/token’ to route XML script documents between various clients calling XMLSERVICE job(s)(/tmp/fred01, /tmp/sally02, etc.).

Behind the scenes of XMLSERVICE the unique IPC provided by any client interface (/tmp/fred01, /tmp/sally02, etc.), establishes a shared memory area for XML information/control passing and a semaphore to provide one-at-time processing in XMLSERVICE job(s). All XMLSERVICE InterProcess Communication activities are keyed and routed to appropriate XMLSERVICE jobs by the IPC name in the /tmp directory (/tmp/fred01, /tmp/sally02, etc.).

As far as security is concerned, all normal IBM i IFS authority rules apply to the IPC access (/tmp/fred01, /tmp/sally02, etc.), therefore assuming no group profile override precedence, only profile fred will be able to access IPC /tmp/fred01 XMLSERVICE job, and only sally profile will be able to access /tmp/sally XMLSERVICE job, and so on for all profiles used. Of course, high authority profiles like *SECOFR have more abilities over *USER profiles, but general rule is the profile you see in wrkactjob “owns/uses” the XMLSERVICE job.

deployment centric, not development centric (caches used XMLSERVICE)

XMLSERVICE is a deployment/production centric service, not development centric, therefore XMLSERVICE internal caches are used to speed up next call processing. XMLSERVICE job(s) caching allows production sites to simply ignore costly operations like XMLSERVICE find/load called modules (your RPG programs), under the assumption that production machine is stable (no recompiles going on).

However, if you are doing development work on your machine (recompiles CL/RPG), you will have to end active/running XMLSERVICE job(s) and restart to call your new program.

Example (actual email): My coworker has told me on more than one occasion that I’m calling his “old” CL or RPG programs even though he recompiled them. I resolve this by restarting the correct XMLSERVICE jobs, allowing me to call the current versions of CL/RPG programs.

Why not use pcml?

pcml falls short in terms of supported RPG types and common attribute conventions (varying) and simply cannot do what this XMLSERVICE custom XML interface is capable of doing. pcml falls even shorter when trying to return complex structures as <return> elements, which is very popular for modern RPG SRVPGMs (XMLSERVICE supports of course). Last, pcml is especially short of the “complete scripting” mark when calling PASE shells, or CMDs (XMLSERVICE supports).

I leave it to the entrepreneurial user to XSLT map PCML to XMLSERVICE (hint).

DB2 XML SQL not work ctl=’*here’?

DB2 SQL XML does not work in-line stateless ($ctl='*here'), but works fine with normal private connections (ipc='/tmp/fred', $ctl='*sbmjob').

Workaround for “bad” drivers leaving junk back of output clob

The world is not perfect 1-2 tier DB2 drivers IBM i, Windows, Linux, etc., so occasionally a “hack” is handy.

I always “scope” my XML input requests with <script>...</script>, so anything past tailing </script> is ‘junk’ (errors return as <report>...</report>).

The new XMLSERVICE keyword *hack adds </hack> back of every record return result set can be very useful for drivers that do not support stored procedure in/out parameters like PHP odbc.

function driverJunkAway($xml)
{
  $clobOut = $xml;
  if (! trim($clobOut)) return $clobOut;

  // result set has extra data (junk)
  $fixme = '</hack>';
  $pos = strpos($clobOut,$fixme);
  if ($pos > -1) {
    $clobOut = substr($clobOut,0,$pos);
  }
  else {
    $fixme = '</script>';
    $pos = strpos($clobOut,$fixme);
    if ($pos > -1) {
      $clobOut = substr($clobOut,0,$pos+strlen($fixme));
    }
    // maybe error/performance report
    else {
      $fixme = '</report>';
      $pos = strpos($clobOut,$fixme);
      if ($pos > -1) {
        $clobOut = substr($clobOut,0,$pos+strlen($fixme));
      }
    }
  }
  return $clobOut;
}

Can i use curl to test XMLSERVICE?

This works from my Linux machine to IBM i.

curl http://myibmi/cgi-bin/xmlcgi.pgm --data-urlencode db2=*LOCAL --data-urlencode uid=*NONE
--data-urlencode pwd=*NONE --data-urlencode ipc=/tmp/rangerhtmlonly --data-urlencode ctl=*sbmjob
--data-urlencode xmlin="<?xml version='1.0'?><script><pgm name='ZZCALL' lib='XMLSERVICE'>
<parm><data type='1A'>a</data></parm><parm><data type='1A'>b</data></parm><parm>
<data type='7p4'>11.1111</data></parm><parm><data type='12p2'>222.22</data></parm><parm><ds>
<data type='1A'>x</data><data type='1A'>y</data><data type='7p4'>66.6666</data>
<data type='12p2'>77777.77</data></ds></parm><return><data type='10i0'>0</data>
</return></pgm></script>" --data-urlencode xmlout=32768

I need to start over kill everything

… doing a lot of machine “updating” test cw/xmlservice versions … many install errors … state unknown … i recommend following actions assuming only Zend Server running this system (be kind to others) …

1) end zendsrvr http

ENDTCPSVR SERVER(*HTTP) INSTANCE(ZENDSVR)

wrkactjob wait all php-cgi to go down, kill php-cgi *immed by hand if won’t die.

2) clear left over IPC attributes for cw/xmlservice test profiles

call qp2term
> ipcs | grep -i qtm | awk '{print "ipcrm -" tolower($1) " "$2}' | sh
> ipcs | grep -i myid1 | awk '{print "ipcrm -" tolower($1) " "$2}' | sh
> ipcs | grep -i myid1 | awk '{print "ipcrm -" tolower($1) " "$2}' | sh
Where:
> qtm - clear IPCs for QTMHHTTP
> myid1 - clear IPCs for profile MYID1
> myid2 - clear IPCs for profile MYID2

3) carefully reset /tmp

call qp2term
> cd /tmp
> pwd
/tmp --- if you do not see /tmp, stop now rm -R may kill whole system
> rm -R *

4) recycle DB2 connections (optional)

ENDPJ SBS(QSYSWRK) PGM(QSQSRVR) OPTION(*IMMED)
STRPJ SBS(QSYSWRK) PGM(QSQSRVR)
  1. re-check configuration

Change these files and restart everything (web, db2, xmlservice, tec.)

I. Apache:  /www/zendsvr/conf/httpd.conf <-- (ILE Apache side)
  DefaultFsCCSID 37   ... or 280 (Italian) ... or so on ...
  CGIJobCCSID 37      ... or 280 (Italian) ... or so on ...

6) restart http

STRTCPSVR SERVER(*HTTP) HTTPSVR(ZENDSVR)

PASE php-cgi out of memory (SetEnv=”LDR_CNTRL=MAXDATA=0x80000000”)

This is a very rare condition, but if you find your huge size PHP script runs out of PASE memory …

You can force most any 32-bit PASE program to leave more heap space (including php-cgi), just specify environment variable up to LDR_CNTRL=MAXDATA=0x80000000 (8 * 256MB). However, please be aware while you are increasing the heap, you are also limiting the number of 256MB segments available for shared memory (not commonly used by PHP programs anyway).

In FastCGI php-cgi just add directive to fastcgi.config.

/www/zendsvr/conf/fastcgi.conf
Server type="application/x-httpd-php" ... normal stuff ... SetEnv="LDR_CNTRL=MAXDATA=0x80000000"

Graphically memory of PASE 32 bit LDR_CNTRL=MAXDATA=0x80000000. Variations of LDR_CNTRL allow you to rearrange even reserved segments and shared libraries, but these uses of LDR_CNTRL are infrequently deployed (except for the most gruesome memory hog Java programs MAXDATA=0xD0000000@DSA).

>export LDR_CNTRL=MAXDATA=0x80000000
>php -v
memory of PASE program PHP
00000000 - 0FFFFFFF - PASE kernel heap (mostly read or no access to user programs)
10000000 - 1FFFFFFF - PASE main program text/code (php)
20000000 - 2FFFFFFF - PASE stack (programs usually share stack/heap in this one segment 256MB)
30000000 - 3FFFFFFF ------
40000000 - 4FFFFFFF      |
50000000 - 5FFFFFFF      |
60000000 - 6FFFFFFF      |--- PASE heap (LDR_CNTRL=MAXDATA=0x80000000)
70000000 - 7FFFFFFF      |    used for new/delete/malloc/free allocations PHP to run scripts
80000000 - 8FFFFFFF      |
90000000 - 9FFFFFFF      |    *Note: no more segments avail for shared memory
A0000000 - AFFFFFFF ------
B0000000 - BFFFFFFF - reserved
C0000000 - CFFFFFFF - reserved
D0000000 - DFFFFFFF - machine wide shared library text/code (libc.a, etc.)
E0000000 - EFFFFFFF - shared memory
F0000000 - FFFFFFFF - machine wide shared library data