Skip to main content

GenericPackager

After writing several Java packager classes and noticing they were all the same boilerplate — an array of ISOFieldPackager objects, one per field — jPOS developer Eoin Flood wrote GenericPackager: a packager that reads field descriptions from an XML file and instantiates the right field packagers at startup.

Performance

Because packagers are created once at startup and shared across all messages, there is no runtime performance difference between a hand-coded Java packager and one driven by GenericPackager. The XML parsing happens only during initialisation.

XML format

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE isopackager PUBLIC
"-//jPOS/jPOS Generic Packager DTD 1.0//EN"
"http://jpos.org/dtd/generic-packager-1.0.dtd">

<isopackager>
<isofield
id="0"
length="4"
name="Message Type Indicator"
class="org.jpos.iso.IFA_NUMERIC"/>

<isofield
id="1"
length="16"
name="Bitmap"
class="org.jpos.iso.IFA_BITMAP"/>

<isofield
id="2"
length="19"
name="Primary Account Number"
class="org.jpos.iso.IFA_LLNUM"/>

<isofield
id="3"
length="6"
name="Processing Code"
class="org.jpos.iso.IFA_NUMERIC"/>

<isofield
id="4"
length="12"
name="Amount, Transaction"
class="org.jpos.iso.IFA_NUMERIC"/>

...

<isofield
id="128"
length="8"
name="Message authentication code"
class="org.jpos.iso.IFA_BINARY"/>
</isopackager>

<isofield> attributes

AttributeRequiredDescription
idYesField number (0–192)
lengthYesMax field length; in chars for IFA_*, in bytes for IFB_BINARY, in hex-chars for IFA_BITMAP
nameYesHuman-readable description (used in log output)
classYesFully qualified ISOFieldPackager class name
padNoMaps to ISOFieldPackager.setPad(true); for IFB_NUMERIC this switches to LEFT_PADDED BCD
trimNoMaps to ISOFieldPackager.setTrim(true); suppresses padding on unpack
tokenNoCustom token attribute for some specialised packagers

<isopackager> attributes

AttributeDefaultDescription
maxValidField128Highest field number accepted
bitmapField1Field number holding the bitmap
emitBitmaptrueWhether to write the bitmap when packing
thirdBitmapField(none)Field number that holds the tertiary (192-bit) bitmap inside a data element
firstField(auto)Override the first field to pack

Built-in packagers

jPOS ships several ready-to-use configurations:

On the classpath (loaded by class name)

ResourceDescription
packager/iso93ascii.xmlISO 8583:1993, full ASCII encoding
packager/iso93binary.xmlISO 8583:1993, BCD numerics, binary bitmap
packager/iso87ascii.xmlISO 8583:1987, ASCII
packager/iso87binary.xmlISO 8583:1987, binary
packager/cmf.xmljPOS Card Management Framework
packager/cmf-858.xmlCMF variant with ISO 8858 extensions

Load from classpath:

ISOPackager p = new GenericPackager("packager/iso93ascii.xml");

External files (in cfg/packager/)

FileDescription
iso87ascii-binary-bitmap.xmlISO 8583:1987, ASCII fields with binary bitmap
iso2003binary.xmlISO 8583:2003 binary
vapvip.xmlVisa/VIP variant
nyceiso8583.xmlNYCE network variant

Load from filesystem:

ISOPackager p = new GenericPackager("cfg/packager/vapvip.xml");

Hand-coded Java alternative

For the rare case where XML isn't expressive enough, you can extend ISOBasePackager directly:

public class ISO93BPackager extends ISOBasePackager {
private static final boolean pad = false;

protected ISOFieldPackager fld[] = {
/*000*/ new IFB_NUMERIC ( 4, "Message Type Indicator", pad),
/*001*/ new IFB_BITMAP (16, "Bitmap"),
/*002*/ new IFB_LLNUM (19, "Primary Account number", pad),
/*003*/ new IFB_NUMERIC ( 6, "Processing Code", pad),
/*004*/ new IFB_NUMERIC (12, "Amount, Transaction", pad),
/*005*/ new IFB_NUMERIC (12, "Amount, Reconciliation", pad),
// ...
/*128*/ new IFB_BINARY ( 8, "Message authentication code")
};

public ISO93BPackager() {
super();
setFieldPackager(fld);
}
}

The fld array is indexed by field number. fld[0] = MTI packager, fld[1] = bitmap packager, fld[2] = field 2 packager, and so on. Gaps (fields defined in the spec but not used in your exchange) can be null or a placeholder.

Wiring a packager to a channel

In a QServer or ChannelAdaptor descriptor:

<channel class="org.jpos.iso.channel.NACChannel"
packager="org.jpos.iso.packager.GenericPackager"
logger="Q2">
<property name="packager-config" value="cfg/packager/myformat.xml"/>
<property name="host" value="..."/>
<property name="port" value="..."/>
</channel>

Or for the XML tutorial packager:

<channel class="org.jpos.iso.channel.XMLChannel" logger="Q2">
<property name="packager" value="org.jpos.iso.packager.XMLPackager"/>
</channel>

Composite fields with <isofieldpackager>

When a field contains its own sub-fields (e.g. field 48 with proprietary sub-elements, or field 55 with EMV TLV data), use <isofieldpackager>:

<isofieldpackager
id="48"
name="Additional Data — Private"
length="999"
class="org.jpos.iso.IFA_LLLCHAR"
packager="org.jpos.iso.packager.GenericSubFieldPackager">

<isofield id="1" length="2" name="Sub-element 1" class="org.jpos.iso.IFA_CHAR"/>
<isofield id="2" length="6" name="Sub-element 2" class="org.jpos.iso.IFA_LLCHAR"/>
<isofield id="3" length="10" name="Sub-element 3" class="org.jpos.iso.IFA_NUMERIC"/>
</isofieldpackager>

What this does:

  1. The outer packager allocates an ISOMsg leaf (field 48) via ISOMsgFieldPackager.createComponent().
  2. The IFA_LLLCHAR outer packager reads/writes the length envelope (3 ASCII digits).
  3. The inner GenericSubFieldPackager processes the payload bytes using the nested <isofield> definitions.

In application code:

ISOMsg de48 = (ISOMsg) msg.getValue(48);
String sub1 = de48.getString(1);
String sub2 = de48.getString(2);

Sub-fields appear as a child ISOMsg nested inside the parent, with their own field numbering starting at 1.

Comparing ASCII vs binary packager XML

The same field definition in both variants side by side:

<!-- ASCII (iso93ascii.xml) -->
<isofield id="0" length="4" name="MTI" class="org.jpos.iso.IFA_NUMERIC"/>
<isofield id="1" length="16" name="Bitmap" class="org.jpos.iso.IFA_BITMAP"/>
<isofield id="2" length="19" name="PAN" class="org.jpos.iso.IFA_LLNUM"/>
<isofield id="4" length="12" name="Amount" class="org.jpos.iso.IFA_NUMERIC"/>
<isofield id="35" length="37" name="Track2" class="org.jpos.iso.IFA_LLCHAR"/>

<!-- Binary (iso93binary.xml) -->
<isofield id="0" length="4" name="MTI" class="org.jpos.iso.IFB_NUMERIC" pad="false"/>
<isofield id="1" length="16" name="Bitmap" class="org.jpos.iso.IFB_BITMAP"/>
<isofield id="2" length="19" name="PAN" class="org.jpos.iso.IFB_LLNUM" pad="false"/>
<isofield id="4" length="12" name="Amount" class="org.jpos.iso.IFB_NUMERIC" pad="false"/>
<isofield id="35" length="37" name="Track2" class="org.jpos.iso.IFB_LLCHAR"/>

For field 2 (a 16-digit PAN):

VariantWire bytesDescription
ASCII1641111111111111110"16" (2 ASCII digits) + "4111111111111111" (16 ASCII chars)
Binary0x16 0x41 0x11 0x11 0x11 0x11 0x11 0x11 0x110x16 = 22 decimal = 16 BCD digits; then 8 bytes of BCD

Wait — 0x16 decoded as BCD is the digit pair 1,6 = decimal 16. The BCD length prefix encodes the number of decimal digits, not bytes.

For field 4 (amount "000000001000", 12 digits):

VariantWire bytesWire size
ASCII00000000100012 bytes
Binary0x00 0x00 0x00 0x10 0x00 0x006 bytes (12 digits / 2)

Binary encoding halves the wire size for numeric fields — significant when multiplied across millions of transactions per day.