64 Commits
Author SHA1 Message Date
Enrique Zamudio 3e714d3409 set version to 1.9.0 2013-11-27 12:19:04 -06:00
Enrique Zamudio ce4f150e8b update documentation for new XML config features 2013-11-27 12:18:47 -06:00
Enrique Zamudio 5413998bb6 update version 2013-11-27 12:18:38 -06:00
Enrique Zamudio d3245ee728 test new method in CompositeField 2013-11-27 12:11:14 -06:00
Enrique Zamudio a4f7ec8696 fixes to CompositeField 2013-11-27 12:10:50 -06:00
Enrique Zamudio 629db2304c test for #17 from xml config 2013-11-27 11:04:54 -06:00
Enrique Zamudio c1c6314144 tests for #17 from xml config 2013-11-27 11:04:35 -06:00
Enrique Zamudio 8f498d3551 binary property is gone 2013-11-27 11:04:19 -06:00
Enrique Zamudio 38b161c1c6 Fix #20 use FieldParseInfo's own CustomField if available 2013-11-27 11:03:47 -06:00
Enrique Zamudio 3b1cf15c3e Parse fields with subfields in parse guides for #17 2013-11-27 11:02:58 -06:00
Enrique Zamudio 5333413e30 improvements to CompositeField 2013-11-27 11:02:19 -06:00
Enrique Zamudio 3d0749687e work on #20 optional CustomField in FieldParseInfo 2013-11-27 11:01:24 -06:00
Enrique Zamudio 32dc578814 refactor common FieldParseInfo creation code 2013-11-26 18:58:40 -06:00
Enrique Zamudio ab740f9efa allow subfiels for #17 2013-11-26 18:50:11 -06:00
Enrique Zamudio 9ecc907739 more thorough tests for composite fields 2013-11-26 18:49:35 -06:00
Enrique Zamudio bec6340fe5 fix length calculation 2013-11-26 18:49:21 -06:00
Enrique Zamudio c9e970904f fix #14 implement parsing guides that extend other guides 2013-11-25 19:49:45 -06:00
Enrique Zamudio eaab64e73f tests for #14 2013-11-25 19:49:25 -06:00
Enrique Zamudio 81f178f870 tests for #15 2013-11-25 19:30:07 -06:00
Enrique Zamudio 8d1e8f389c Implement #15 templates that extend other templates 2013-11-25 19:29:45 -06:00
Enrique Zamudio 8dd44c4ae3 accept type "excluded" for extending templates 2013-11-25 19:28:55 -06:00
Enrique Zamudio 42e17fb036 refactor for #14 and #15 2013-11-25 19:05:41 -06:00
Enrique Zamudio 80dd7f78ec work on #14 2013-11-25 18:56:43 -06:00
Enrique Zamudio 041c058b33 work on #15 2013-11-25 18:56:27 -06:00
Enrique Zamudio c00ef7b88d tests for #16 2013-11-25 18:53:06 -06:00
Enrique Zamudio a05373efec tests for #16 2013-11-25 18:52:30 -06:00
Enrique Zamudio 5c53676cda process ref attribute of header element for #16 2013-11-25 18:51:57 -06:00
Enrique Zamudio 759ec1497b add "ref" attribute to "header" element for #16 2013-11-25 18:51:33 -06:00
Enrique Zamudio d99279ec7e tests for #17 2013-11-25 18:26:13 -06:00
Enrique Zamudio 515724bd7e decoding CompositeField for #17 2013-11-25 18:25:49 -06:00
Enrique Zamudio 59365dae25 set jdk 6 2013-11-25 17:56:52 -06:00
Enrique Zamudio fd2f9e8103 work on CompositeField for #17 2013-11-25 17:56:26 -06:00
Enrique Zamudio 07b277c7fa Tests for #17 2013-11-25 17:55:59 -06:00
Enrique Zamudio 3a4840108d more BCD tests 2013-11-25 11:01:06 -06:00
Enrique Zamudio 8edbad8659 BCD tests 2013-11-25 10:55:08 -06:00
Enrique Zamudio 2c466a76c5 Add LGPL notice 2013-07-15 13:53:24 -05:00
Enrique Zamudio df5c1d7bd2 version 1.8.0 2013-06-25 12:56:45 -05:00
Enrique Zamudio 16858e2f42 version 1.8.0 2013-06-25 12:53:15 -05:00
Enrique Zamudio cc1fe845ed Version 1.8.0 2013-06-25 12:39:13 -05:00
Enrique Zamudio 61fded16d2 Fix debugString for variable-length fields 2013-06-25 12:37:41 -05:00
Enrique Zamudio 6bdd26fadf update site version to 1.8.0 2013-06-12 13:18:05 -05:00
Enrique Zamudio 0a85393b87 Simplify test code 2013-06-12 13:17:34 -05:00
Enrique Zamudio 1ccf3596d8 Test parsing a complete message with EBCDIC 2013-06-12 13:17:18 -05:00
Enrique Zamudio 470d492da2 Propagate char encoding and forceStringEncoding after parsing config 2013-06-12 13:16:28 -05:00
Enrique Zamudio a543212d1e debugString uses substrings for bitmap hex chars 2013-06-12 13:10:35 -05:00
Enrique Zamudio 096faeeda0 indexOf -> contains 2013-06-12 13:09:22 -05:00
Enrique Zamudio 406eb91d4b Simplify hex char array creation 2013-06-12 13:08:58 -05:00
Enrique Zamudio 8f91cb768c Test debugString with binary/ascii messages 2013-06-12 10:42:27 -05:00
Enrique Zamudio 078eeb6ee5 Add debugString() method 2013-06-12 10:42:09 -05:00
Enrique Zamudio 1b38a648f5 Set forceStringEncoding property on new messages 2013-06-12 10:41:50 -05:00
Enrique Zamudio 29357b8dd2 set jdk to 7 (with java6 compat) 2013-06-12 10:39:30 -05:00
Enrique Zamudio 1ce67c621c Encode text bitmap in EBCDIC with forceStringEncoding=true #13 2013-05-13 23:13:44 -05:00
Enrique Zamudio 1787dfb0d5 Test EBCDIC bitmap 2013-05-13 23:12:59 -05:00
Enrique Zamudio 2b1f9628bf Parse text bitmap for #13 2013-05-13 20:03:15 -05:00
Enrique Zamudio 2a38ac2491 force radix 10 2013-05-13 19:48:12 -05:00
Enrique Zamudio 63e89e708b add forceStringEncoding property to pass to messages for #13 2013-05-13 19:47:48 -05:00
Enrique Zamudio a086cad36d Test message parsing for #13 2013-05-13 19:47:27 -05:00
Enrique Zamudio 11e10bc1eb Implement #13 in these parsers 2013-05-13 19:34:21 -05:00
Enrique Zamudio e5aa694a45 More tests for #13 2013-05-13 19:34:04 -05:00
Enrique Zamudio 38df434d0a raise version number 2013-05-13 18:59:29 -05:00
Enrique Zamudio 89d8fce075 Test proper encoding of message type for #13 2013-05-13 18:59:09 -05:00
Enrique Zamudio 06700670ea update API calls 2013-05-13 18:55:01 -05:00
Enrique Zamudio 2e521c2b6d Implement #13 2013-05-13 18:54:44 -05:00
Enrique Zamudio 160f780309 Tests for #13 2013-05-13 18:53:20 -05:00
37 changed files with 1437 additions and 231 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
apply plugin:'java'
apply plugin: 'maven'
defaultTasks 'build'
version='1.7.0'
version='1.9.0'
description='Java implementation of the ISO 8583 protocol, focused on making the creation, edition and reading of ISO8583 messages as simple and flexible as possible.'
sourceCompatibility=6
targetCompatibility=6
+2 -2
View File
@@ -98,8 +98,8 @@
</option>
</component>
<component name="GradleSettings">
<option name="gradleHome" value="$PROJECT_DIR$/../../../gradle-1.4" />
<option name="preferLocalInstallationToWrapper" value="true" />
<option name="gradleHome" value="$PROJECT_DIR$/../../../gradle-1.8" />
<option name="linkedProjectPath" value="" />
</component>
<component name="GradleUISettings2">
<setting name="root" />
+1 -1
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>net.sf.j8583</groupId>
<artifactId>j8583</artifactId>
<version>1.5.8-SNAPSHOT</version>
<version>1.9.0</version>
<packaging>jar</packaging>
<name>j8583</name>
<description>Java implementation of the ISO 8583 protocol, focused on making the creation, edition and reading of ISO8583 messages as simple and flexible as possible.</description>
@@ -1,3 +1,21 @@
/*
* j8583 A Java implementation of the ISO8583 protocol
* Copyright (C) 2007 Enrique Zamudio Lopez
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package com.solab.iso8583;
/**
+88 -17
View File
@@ -51,6 +51,7 @@ public class IsoMessage {
/** Flag to enforce secondary bitmap even if empty. */
private boolean forceb2;
private boolean binBitmap;
private boolean forceStringEncoding;
private String encoding = System.getProperty("file.encoding");
/** Creates a new empty message with no values set. */
@@ -96,7 +97,15 @@ public class IsoMessage {
return encoding;
}
/** Sets the string to be sent as ISO header, that is, after the length header but before the message type.
/** Specified whether the variable-length fields should encode their length
* headers using string conversion with the proper character encoding. Default
* is false, which is the old behavior (encoding as ASCII). This is only useful
* for text format. */
public void setForceStringEncoding(boolean flag) {
forceStringEncoding = flag;
}
/** Sets the string to be sent as ISO header, that is, after the length header but before the message type.
* This is useful in case an application needs some custom data in the ISO header of each message (very rare). */
public void setIsoHeader(String value) {
isoHeader = value;
@@ -309,6 +318,26 @@ public class IsoMessage {
return buf;
}
/** Creates a BitSet for the bitmap. */
protected BitSet createBitmapBitSet() {
BitSet bs = new BitSet(forceb2 ? 128 : 64);
for (int i = 2 ; i < 129; i++) {
if (fields[i] != null) {
bs.set(i - 1);
}
}
if (forceb2) {
bs.set(0);
} else if (bs.length() > 64) {
//Extend to 128 if needed
BitSet b2 = new BitSet(128);
b2.or(bs);
bs = b2;
bs.set(0);
}
return bs;
}
/** This calls writeInternal(), allowing applications to get the byte buffer containing the
* message data, without the length header. */
public byte[] writeData() {
@@ -333,21 +362,7 @@ public class IsoMessage {
}
//Bitmap
BitSet bs = new BitSet(forceb2 ? 128 : 64);
for (int i = 2 ; i < 129; i++) {
if (fields[i] != null) {
bs.set(i - 1);
}
}
if (forceb2) {
bs.set(0);
} else if (bs.length() > 64) {
//Extend to 128 if needed
BitSet b2 = new BitSet(128);
b2.or(bs);
bs = b2;
bs.set(0);
}
BitSet bs = createBitmapBitSet();
//Write bitmap to stream
if (binary || binBitmap) {
int pos = 128;
@@ -364,6 +379,11 @@ public class IsoMessage {
}
}
} else {
ByteArrayOutputStream bout2 = null;
if (forceStringEncoding) {
bout2 = bout;
bout = new ByteArrayOutputStream();
}
int pos = 0;
int lim = bs.size() / 4;
for (int i = 0; i < lim; i++) {
@@ -378,6 +398,15 @@ public class IsoMessage {
nibble |= 1;
bout.write(HEX[nibble]);
}
if (forceStringEncoding) {
final String _hb = new String(bout.toByteArray());
bout = bout2;
try {
bout.write(_hb.getBytes(encoding));
} catch (IOException ignore) {
//never happen
}
}
}
//Fields
@@ -385,7 +414,7 @@ public class IsoMessage {
IsoValue<?> v = fields[i];
if (v != null) {
try {
v.write(bout, binary);
v.write(bout, binary, forceStringEncoding);
} catch (IOException ex) {
//should never happen, writing to a ByteArrayOutputStream
}
@@ -394,6 +423,48 @@ public class IsoMessage {
return bout.toByteArray();
}
/** Returns a string representation of the message, as if it were encoded
* in ASCII with no binary bitmap. */
public String debugString() {
StringBuilder sb = new StringBuilder();
if (isoHeader != null) {
sb.append(isoHeader);
}
sb.append(String.format("%04x", type));
//Bitmap
BitSet bs = createBitmapBitSet();
int pos = 0;
int lim = bs.size() / 4;
for (int i = 0; i < lim; i++) {
int nibble = 0;
if (bs.get(pos++))
nibble |= 8;
if (bs.get(pos++))
nibble |= 4;
if (bs.get(pos++))
nibble |= 2;
if (bs.get(pos++))
nibble |= 1;
sb.append(new String(HEX, nibble, 1));
}
//Fields
for (int i = 2; i < 129; i++) {
IsoValue<?> v = fields[i];
if (v != null) {
String desc = v.toString();
if (v.getType() == IsoType.LLBIN || v.getType() == IsoType.LLVAR) {
sb.append(String.format("%02d", desc.length()));
} else if (v.getType() == IsoType.LLLBIN || v.getType() == IsoType.LLLVAR) {
sb.append(String.format("%03d", desc.length()));
}
sb.append(desc);
}
}
return sb.toString();
}
//These are for Groovy compat
/** Sets the specified value in the specified field, just like {@link #setField(int, IsoValue)}. */
public <T> void putAt(int i, IsoValue<T> v) {
+41 -39
View File
@@ -245,48 +245,50 @@ public class IsoValue<T> implements Cloneable {
return encoder;
}
protected void writeLengthHeader(final int l, final OutputStream outs, final int digits,
final boolean binary, final boolean forceStringEncoding)
throws IOException {
if (binary) {
if (digits == 3) {
outs.write(l / 100); //00 to 09 automatically in BCD
}
//BCD encode the rest of the length
outs.write((((l % 100) / 10) << 4) | (l % 10));
} else if (forceStringEncoding) {
String lhead = Integer.toString(l);
final int ldiff = digits - lhead.length();
if (ldiff == 1) {
lhead = '0' + lhead;
} else if (ldiff == 2) {
lhead = "00" + lhead;
}
outs.write(lhead.getBytes(encoding));
} else {
//write the length in ASCII
if (digits == 3) {
outs.write((l / 100) + 48);
}
if (l >= 10) {
outs.write(((l % 100) / 10) + 48);
} else {
outs.write(48);
}
outs.write((l % 10) + 48);
}
}
/** Writes the formatted value to a stream, with the length header
* if it's a variable length type. */
public void write(OutputStream outs, boolean binary) throws IOException {
* if it's a variable length type.
* @param outs The stream to which the value will be written.
* @param binary Specifies whether the value should be written in binary or text format.
* @param forceStringEncoding When using text format, force the encoding of length headers
* for variable-length fields to be done with the proper character encoding. When false,
* the length headers are encoded as ASCII; this used to be the only behavior. */
public void write(final OutputStream outs, final boolean binary, final boolean forceStringEncoding) throws IOException {
if (type == IsoType.LLLVAR || type == IsoType.LLVAR) {
if (binary) {
if (type == IsoType.LLLVAR) {
outs.write(length / 100); //00 to 09 automatically in BCD
}
//BCD encode the rest of the length
outs.write((((length % 100) / 10) << 4) | (length % 10));
} else {
//write the length in ASCII
if (type == IsoType.LLLVAR) {
outs.write((length / 100) + 48);
}
if (length >= 10) {
outs.write(((length % 100) / 10) + 48);
} else {
outs.write(48);
}
outs.write((length % 10) + 48);
}
writeLengthHeader(length, outs, type == IsoType.LLLVAR ? 3 : 2, binary, forceStringEncoding);
} else if (type == IsoType.LLBIN || type == IsoType.LLLBIN) {
if (binary) {
if (type == IsoType.LLLBIN ) {
outs.write(length / 100); //00 to 09 automatically in BCD
}
//BCD encode the rest of the length
outs.write((((length % 100) / 10) << 4) | (length % 10));
} else {
int _l = length*2;
//write the length in ASCII
if (type == IsoType.LLLBIN) {
outs.write((_l / 100) + 48);
}
if (_l >= 10) {
outs.write(((_l % 100) / 10) + 48);
} else {
outs.write(48);
}
outs.write((_l % 10) + 48);
}
writeLengthHeader(binary ? length : length*2, outs, type == IsoType.LLLBIN ? 3 : 2, binary, forceStringEncoding);
} else if (binary) {
//numeric types in binary are coded like this
byte[] buf = null;
@@ -75,8 +75,23 @@ public class MessageFactory<T extends IsoMessage> {
private boolean ignoreLast;
private boolean forceb2;
private boolean binBitmap;
private boolean forceStringEncoding;
private String encoding = System.getProperty("file.encoding");
/** This flag gets passed on to newly created messages and also sets this value for all
* field parsers in parsing guides. */
public void setForceStringEncoding(boolean flag) {
forceStringEncoding = flag;
for (Map<Integer,FieldParseInfo> pm : parseMap.values()) {
for (FieldParseInfo parser : pm.values()) {
parser.setForceStringDecoding(flag);
}
}
}
public boolean isForceStringEncoding() {
return forceStringEncoding;
}
/** Tells the factory to create messages that encode their bitmaps in binary format
* even when they're encoded as text. Has no effect on binary messages. */
public void setUseBinaryBitmap(boolean flag) {
@@ -94,13 +109,24 @@ public class MessageFactory<T extends IsoMessage> {
throw new IllegalArgumentException("Cannot set null encoding.");
}
encoding = value;
if (parseMap.size() > 0) {
if (!parseMap.isEmpty()) {
for (Map<Integer, FieldParseInfo> pt : parseMap.values()) {
for (FieldParseInfo fpi : pt.values()) {
fpi.setCharacterEncoding(encoding);
}
}
}
if (!typeTemplates.isEmpty()) {
for (T tmpl : typeTemplates.values()) {
tmpl.setCharacterEncoding(encoding);
for (int i = 2 ; i<129; i++) {
IsoValue<?> v = tmpl.getField(i);
if (v != null) {
v.setCharacterEncoding(encoding);
}
}
}
}
}
/** Returns the encoding used to parse ALPHA, LLVAR and LLLVAR fields. The default is the file.encoding
@@ -113,7 +139,7 @@ public class MessageFactory<T extends IsoMessage> {
public void setForceSecondaryBitmap(boolean flag) {
forceb2 = flag;
}
public boolean getForceSecondaryBitmap() {
public boolean isForceSecondaryBitmap() {
return forceb2;
}
@@ -143,12 +169,12 @@ public class MessageFactory<T extends IsoMessage> {
}
/** Returns a custom field encoder/decoder for the specified field number, if one is available. */
@SuppressWarnings("unchecked")
public <T> CustomField<T> getCustomField(int index) {
public <F> CustomField<F> getCustomField(int index) {
return customFields.get(index);
}
/** Returns a custom field encoder/decoder for the specified field number, if one is available. */
@SuppressWarnings("unchecked")
public <T> CustomField<T> getCustomField(Integer index) {
public <F> CustomField<F> getCustomField(Integer index) {
return customFields.get(index);
}
@@ -157,6 +183,9 @@ public class MessageFactory<T extends IsoMessage> {
* but is really convenient in case the MessageFactory is being configured from within, say, Spring. */
public void setConfigPath(String path) throws IOException {
ConfigParser.configureFromClasspathConfig(this, path);
//Now re-set some properties that need to be propagated down to the recently assigned objects
setCharacterEncoding(encoding);
setForceStringEncoding(forceStringEncoding);
}
/** Tells the receiver to create and parse binary messages if the flag is true.
@@ -192,6 +221,7 @@ public class MessageFactory<T extends IsoMessage> {
m.setForceSecondaryBitmap(forceb2);
m.setBinaryBitmap(binBitmap);
m.setCharacterEncoding(encoding);
m.setForceStringEncoding(forceStringEncoding);
//Copy the values from the template
IsoMessage templ = typeTemplates.get(type);
@@ -257,11 +287,13 @@ public class MessageFactory<T extends IsoMessage> {
if (buf.length < minlength) {
throw new ParseException("Insufficient buffer length, needs to be at least " + minlength, 0);
}
final T m = createIsoMessage(isoHeaderLength > 0 ? new String(buf, 0, isoHeaderLength) : null);
final T m = createIsoMessage(isoHeaderLength > 0 ? new String(buf, 0, isoHeaderLength, encoding) : null);
m.setCharacterEncoding(encoding);
final int type;
if (useBinary) {
type = ((buf[isoHeaderLength] & 0xff) << 8) | (buf[isoHeaderLength + 1] & 0xff);
} else if (forceStringEncoding) {
type = Integer.parseInt(new String(buf, isoHeaderLength, 4, encoding), 16);
} else {
type = ((buf[isoHeaderLength] - 48) << 12)
| ((buf[isoHeaderLength + 1] - 48) << 8)
@@ -300,45 +332,57 @@ public class MessageFactory<T extends IsoMessage> {
} else {
//ASCII parsing
try {
for (int i = isoHeaderLength + 4; i < isoHeaderLength + 20; i++) {
if (buf[i] >= '0' && buf[i] <= '9') {
bs.set(pos++, ((buf[i] - 48) & 8) > 0);
bs.set(pos++, ((buf[i] - 48) & 4) > 0);
bs.set(pos++, ((buf[i] - 48) & 2) > 0);
bs.set(pos++, ((buf[i] - 48) & 1) > 0);
} else if (buf[i] >= 'A' && buf[i] <= 'F') {
bs.set(pos++, ((buf[i] - 55) & 8) > 0);
bs.set(pos++, ((buf[i] - 55) & 4) > 0);
bs.set(pos++, ((buf[i] - 55) & 2) > 0);
bs.set(pos++, ((buf[i] - 5) & 1) > 0);
} else if (buf[i] >= 'a' && buf[i] <= 'f') {
bs.set(pos++, ((buf[i] - 87) & 8) > 0);
bs.set(pos++, ((buf[i] - 87) & 4) > 0);
bs.set(pos++, ((buf[i] - 87) & 2) > 0);
bs.set(pos++, ((buf[i] - 87) & 1) > 0);
}
}
final byte[] bitmapBuffer;
if (forceStringEncoding) {
byte[] _bb = new String(buf, isoHeaderLength+4, 16, encoding).getBytes();
bitmapBuffer = new byte[36+isoHeaderLength];
System.arraycopy(_bb, 0, bitmapBuffer, 4+isoHeaderLength, 16);
} else {
bitmapBuffer = buf;
}
for (int i = isoHeaderLength + 4; i < isoHeaderLength + 20; i++) {
if (bitmapBuffer[i] >= '0' && bitmapBuffer[i] <= '9') {
bs.set(pos++, ((bitmapBuffer[i] - 48) & 8) > 0);
bs.set(pos++, ((bitmapBuffer[i] - 48) & 4) > 0);
bs.set(pos++, ((bitmapBuffer[i] - 48) & 2) > 0);
bs.set(pos++, ((bitmapBuffer[i] - 48) & 1) > 0);
} else if (bitmapBuffer[i] >= 'A' && bitmapBuffer[i] <= 'F') {
bs.set(pos++, ((bitmapBuffer[i] - 55) & 8) > 0);
bs.set(pos++, ((bitmapBuffer[i] - 55) & 4) > 0);
bs.set(pos++, ((bitmapBuffer[i] - 55) & 2) > 0);
bs.set(pos++, ((bitmapBuffer[i] - 5) & 1) > 0);
} else if (bitmapBuffer[i] >= 'a' && bitmapBuffer[i] <= 'f') {
bs.set(pos++, ((bitmapBuffer[i] - 87) & 8) > 0);
bs.set(pos++, ((bitmapBuffer[i] - 87) & 4) > 0);
bs.set(pos++, ((bitmapBuffer[i] - 87) & 2) > 0);
bs.set(pos++, ((bitmapBuffer[i] - 87) & 1) > 0);
}
}
//Check for secondary bitmap and parse it if necessary
if (bs.get(0)) {
if (buf.length < minlength + 16) {
throw new ParseException("Insufficient length for secondary bitmap", minlength);
}
if (forceStringEncoding) {
byte[] _bb = new String(buf, isoHeaderLength+4, 16, encoding).getBytes();
System.arraycopy(_bb, 0, bitmapBuffer, 20+isoHeaderLength, 16);
}
for (int i = isoHeaderLength + 20; i < isoHeaderLength + 36; i++) {
if (buf[i] >= '0' && buf[i] <= '9') {
bs.set(pos++, ((buf[i] - 48) & 8) > 0);
bs.set(pos++, ((buf[i] - 48) & 4) > 0);
bs.set(pos++, ((buf[i] - 48) & 2) > 0);
bs.set(pos++, ((buf[i] - 48) & 1) > 0);
} else if (buf[i] >= 'A' && buf[i] <= 'F') {
bs.set(pos++, ((buf[i] - 55) & 8) > 0);
bs.set(pos++, ((buf[i] - 55) & 4) > 0);
bs.set(pos++, ((buf[i] - 55) & 2) > 0);
bs.set(pos++, ((buf[i] - 5) & 1) > 0);
} else if (buf[i] >= 'a' && buf[i] <= 'f') {
bs.set(pos++, ((buf[i] - 87) & 8) > 0);
bs.set(pos++, ((buf[i] - 87) & 4) > 0);
bs.set(pos++, ((buf[i] - 87) & 2) > 0);
bs.set(pos++, ((buf[i] - 87) & 1) > 0);
if (bitmapBuffer[i] >= '0' && bitmapBuffer[i] <= '9') {
bs.set(pos++, ((bitmapBuffer[i] - 48) & 8) > 0);
bs.set(pos++, ((bitmapBuffer[i] - 48) & 4) > 0);
bs.set(pos++, ((bitmapBuffer[i] - 48) & 2) > 0);
bs.set(pos++, ((bitmapBuffer[i] - 48) & 1) > 0);
} else if (bitmapBuffer[i] >= 'A' && bitmapBuffer[i] <= 'F') {
bs.set(pos++, ((bitmapBuffer[i] - 55) & 8) > 0);
bs.set(pos++, ((bitmapBuffer[i] - 55) & 4) > 0);
bs.set(pos++, ((bitmapBuffer[i] - 55) & 2) > 0);
bs.set(pos++, ((bitmapBuffer[i] - 5) & 1) > 0);
} else if (bitmapBuffer[i] >= 'a' && bitmapBuffer[i] <= 'f') {
bs.set(pos++, ((bitmapBuffer[i] - 87) & 8) > 0);
bs.set(pos++, ((bitmapBuffer[i] - 87) & 4) > 0);
bs.set(pos++, ((bitmapBuffer[i] - 87) & 2) > 0);
bs.set(pos++, ((bitmapBuffer[i] - 87) & 1) > 0);
}
}
pos = 16 + minlength;
@@ -379,7 +423,11 @@ public class MessageFactory<T extends IsoMessage> {
log.warn("Field {} is not really in the message even though it's in the bitmap", i);
bs.clear(i - 1);
} else {
IsoValue<?> val = fpi.parseBinary(i, buf, pos, getCustomField(i));
CustomField<?> decoder = fpi.getDecoder();
if (decoder == null) {
decoder = getCustomField(i);
}
IsoValue<?> val = fpi.parseBinary(i, buf, pos, decoder);
m.setField(i, val);
if (val != null) {
if (val.getType() == IsoType.NUMERIC || val.getType() == IsoType.DATE10
@@ -406,7 +454,11 @@ public class MessageFactory<T extends IsoMessage> {
log.warn("Field {} is not really in the message even though it's in the bitmap", i);
bs.clear(i - 1);
} else {
IsoValue<?> val = fpi.parse(i, buf, pos, getCustomField(i));
CustomField<?> decoder = fpi.getDecoder();
if (decoder == null) {
decoder = getCustomField(i);
}
IsoValue<?> val = fpi.parse(i, buf, pos, decoder);
m.setField(i, val);
//To get the correct next position, we need to get the number of bytes, not chars
pos += val.toString().getBytes(fpi.getCharacterEncoding()).length;
@@ -426,7 +478,7 @@ public class MessageFactory<T extends IsoMessage> {
/** Creates a Iso message, override this method in the subclass to provide your
* own implementations of IsoMessage.
* @param header
* @param header The optional ISO header that goes before the message type
* @return IsoMessage
*/
@SuppressWarnings("unchecked")
@@ -1,3 +1,21 @@
/*
* j8583 A Java implementation of the ISO8583 protocol
* Copyright (C) 2007 Enrique Zamudio Lopez
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package com.solab.iso8583.codecs;
import com.solab.iso8583.CustomBinaryField;
@@ -0,0 +1,192 @@
package com.solab.iso8583.codecs;
import com.solab.iso8583.CustomBinaryField;
import com.solab.iso8583.CustomField;
import com.solab.iso8583.IsoType;
import com.solab.iso8583.IsoValue;
import com.solab.iso8583.parse.FieldParseInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
/**
* A codec to manage subfields inside a field of a certain type.
*
* @author Enrique Zamudio
* Date: 25/11/13 11:25
*/
public class CompositeField implements CustomBinaryField<CompositeField> {
private static final Logger log = LoggerFactory.getLogger(CompositeField.class);
/** Stores the subfields. */
@SuppressWarnings("rawtypes")
private List<IsoValue> values;
/** Stores the parsers for the subfields. */
private List<FieldParseInfo> parsers;
@SuppressWarnings("rawtypes")
public void setValues(List<IsoValue> values) {
this.values = values;
}
@SuppressWarnings("rawtypes")
public List<IsoValue> getValues() {
return values;
}
@SuppressWarnings("rawtypes")
public CompositeField addValue(IsoValue<?> v) {
if (values == null) {
values = new ArrayList<IsoValue>(4);
}
values.add(v);
return this;
}
public <T> CompositeField addValue(T val, CustomField<T> encoder, IsoType t, int length) {
return addValue(t.needsLength() ? new IsoValue<T>(t, val, length, encoder)
: new IsoValue<T>(t, val, encoder));
}
@SuppressWarnings("unchecked")
public <T> IsoValue<T> getField(int idx) {
if (idx < 0 || idx > values.size())return null;
return values.get(idx);
}
public <T> T getObjectValue(int idx) {
IsoValue<T> v = getField(idx);
return v==null ? null : v.getValue();
}
public void setParsers(List<FieldParseInfo> fpis) {
parsers = fpis;
}
public List<FieldParseInfo> getParsers() {
return parsers;
}
public CompositeField addParser(FieldParseInfo fpi) {
if (parsers == null) {
parsers = new ArrayList<FieldParseInfo>(4);
}
parsers.add(fpi);
return this;
}
@Override
public CompositeField decodeBinaryField(byte[] buf, int offset, int length) {
@SuppressWarnings("rawtypes")
List<IsoValue> vals = new ArrayList<IsoValue>(parsers.size());
int pos = 0;
try {
for (FieldParseInfo fpi : parsers) {
IsoValue<?> v = fpi.parseBinary(0, buf, pos, null);
if (v != null) {
if (v.getType() == IsoType.NUMERIC || v.getType() == IsoType.DATE10
|| v.getType() == IsoType.DATE4 || v.getType() == IsoType.DATE_EXP
|| v.getType() == IsoType.AMOUNT || v.getType() == IsoType.TIME) {
pos += (v.getLength() / 2) + (v.getLength() % 2);
} else {
pos += v.getLength();
}
if (v.getType() == IsoType.LLVAR || v.getType() == IsoType.LLBIN) {
pos++;
} else if (v.getType() == IsoType.LLLVAR || v.getType() == IsoType.LLLBIN) {
pos+=2;
}
vals.add(v);
}
}
final CompositeField f = new CompositeField();
f.setValues(vals);
return f;
} catch (ParseException ex) {
log.error("Decoding binary CompositeField", ex);
return null;
} catch (UnsupportedEncodingException ex) {
log.error("Decoding binary CompositeField", ex);
return null;
}
}
@Override
public CompositeField decodeField(String value) {
@SuppressWarnings("rawtypes")
List<IsoValue> vals = new ArrayList<IsoValue>(parsers.size());
byte[] buf = value.getBytes();
int pos = 0;
try {
for (FieldParseInfo fpi : parsers) {
IsoValue<?> v = fpi.parse(0, buf, pos, null);
if (v != null) {
pos += v.toString().getBytes(fpi.getCharacterEncoding()).length;
if (v.getType() == IsoType.LLVAR || v.getType() == IsoType.LLBIN) {
pos+=2;
} else if (v.getType() == IsoType.LLLVAR || v.getType() == IsoType.LLLBIN) {
pos+=3;
}
vals.add(v);
}
}
final CompositeField f = new CompositeField();
f.setValues(vals);
return f;
} catch (ParseException ex) {
log.error("Decoding CompositeField", ex);
return null;
} catch (UnsupportedEncodingException ex) {
log.error("Decoding CompositeField", ex);
return null;
}
}
@Override
public byte[] encodeBinaryField(CompositeField value) {
final ByteArrayOutputStream bout = new ByteArrayOutputStream();
try {
for (IsoValue<?> v : value.getValues()) {
v.write(bout, true, true);
}
} catch (IOException ex) {
log.error("Encoding binary CompositeField", ex);
//shouldn't happen
}
return bout.toByteArray();
}
@Override
public String encodeField(CompositeField value) {
final ByteArrayOutputStream bout = new ByteArrayOutputStream();
byte[] buf = null;
try {
String encoding = null;
for (IsoValue<?> v : value.getValues()) {
v.write(bout, false, true);
if (encoding == null)encoding = v.getCharacterEncoding();
}
buf = bout.toByteArray();
return new String(buf, encoding==null?"UTF-8":encoding);
} catch (UnsupportedEncodingException ex) {
log.error("Encoding text CompositeField", ex);
} catch (IOException ex) {
log.error("Encoding text CompositeField", ex);
//shouldn't happen
}
return new String(buf);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("CompositeField[");
if (values!=null) {
boolean first=true;
for (IsoValue<?> v : values) {
if (first)first=false;else sb.append(',');
sb.append(v.getType());
}
}
return sb.append(']').toString();
}
}
@@ -1,3 +1,21 @@
/*
* j8583 A Java implementation of the ISO8583 protocol
* Copyright (C) 2007 Enrique Zamudio Lopez
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package com.solab.iso8583.codecs;
import com.solab.iso8583.CustomBinaryField;
@@ -21,12 +21,14 @@ package com.solab.iso8583.parse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import com.solab.iso8583.codecs.CompositeField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
@@ -106,6 +108,224 @@ public class ConfigParser {
return mfact;
}
protected static <T extends IsoMessage> void parseHeaders(
final NodeList nodes, final MessageFactory<T> mfact) throws IOException {
ArrayList<Element> refs = null;
for (int i = 0; i < nodes.getLength(); i++) {
Element elem = (Element)nodes.item(i);
int type = parseType(elem.getAttribute("type"));
if (type == -1) {
throw new IOException("Invalid type for ISO8583 header: " + elem.getAttribute("type"));
}
if (elem.getChildNodes() == null || elem.getChildNodes().getLength() == 0) {
if (elem.getAttribute("ref") != null && !elem.getAttribute("ref").isEmpty()) {
if (refs == null) {
refs = new ArrayList<Element>(nodes.getLength()-i);
}
refs.add(elem);
}
else throw new IOException("Invalid ISO8583 header element");
} else {
String header = elem.getChildNodes().item(0).getNodeValue();
if (log.isTraceEnabled()) {
log.trace("Adding ISO8583 header for type {}: {}", elem.getAttribute("type"), header);
}
mfact.setIsoHeader(type, header);
}
}
if (refs != null) {
for (Element elem : refs) {
int type = parseType(elem.getAttribute("type"));
if (type == -1) {
throw new IOException("Invalid type for ISO8583 header: "
+ elem.getAttribute("type"));
}
if (elem.getAttribute("ref") != null && !elem.getAttribute("ref").isEmpty()) {
int t2 = parseType(elem.getAttribute("ref"));
if (t2 == -1) {
throw new IOException("Invalid type reference "
+ elem.getAttribute("ref") + " for ISO8583 header " + type);
}
String h = mfact.getIsoHeader(t2);
if (h == null) {
throw new IllegalArgumentException("Header def " + type + " refers to nonexistent header " + t2);
}
if (log.isTraceEnabled()) {
log.trace("Adding ISO8583 header for type {}: {} (copied from {})",
elem.getAttribute("type"), h, elem.getAttribute("ref"));
}
mfact.setIsoHeader(type, h);
}
}
}
}
protected static <T extends IsoMessage> void parseTemplates(
final NodeList nodes, final MessageFactory<T> mfact) throws IOException {
ArrayList<Element> subs = null;
for (int i = 0; i < nodes.getLength(); i++) {
Element elem = (Element)nodes.item(i);
int type = parseType(elem.getAttribute("type"));
if (type == -1) {
throw new IOException("Invalid ISO8583 type for template: " + elem.getAttribute("type"));
}
if (elem.getAttribute("extends") != null && !elem.getAttribute("extends").isEmpty()) {
if (subs == null) {
subs = new ArrayList<Element>(nodes.getLength()-i);
}
subs.add(elem);
continue;
}
@SuppressWarnings("unchecked")
T m = (T)new IsoMessage();
m.setType(type);
m.setCharacterEncoding(mfact.getCharacterEncoding());
NodeList fields = elem.getElementsByTagName("field");
for (int j = 0; j < fields.getLength(); j++) {
Element f = (Element)fields.item(j);
int num = Integer.parseInt(f.getAttribute("num"));
IsoType itype = IsoType.valueOf(f.getAttribute("type"));
int length = 0;
if (f.getAttribute("length").length() > 0) {
length = Integer.parseInt(f.getAttribute("length"));
}
String v = f.getChildNodes().item(0).getNodeValue();
CustomField<Object> _cf = mfact.getCustomField(num);
m.setValue(num, _cf == null ? v : _cf.decodeField(v), _cf, itype, length);
}
mfact.addMessageTemplate(m);
}
if (subs != null) {
for (Element elem : subs) {
int type = parseType(elem.getAttribute("type"));
int ref = parseType(elem.getAttribute("extends"));
if (ref == -1) {
throw new IllegalArgumentException("Message template "
+ elem.getAttribute("type") + " extends invalid template "
+ elem.getAttribute("extends"));
}
T tref = mfact.getMessageTemplate(ref);
if (tref == null) {
throw new IllegalArgumentException("Message template "
+ elem.getAttribute("type") + " extends nonexistent template "
+ elem.getAttribute("extends"));
}
@SuppressWarnings("unchecked")
T m = (T)new IsoMessage();
m.setType(type);
m.setCharacterEncoding(mfact.getCharacterEncoding());
for (int i = 2; i < 128; i++) {
if (tref.hasField(i)) {
m.setField(i, tref.getField(i).clone());
}
}
NodeList fields = elem.getElementsByTagName("field");
for (int j = 0; j < fields.getLength(); j++) {
Element f = (Element)fields.item(j);
int num = Integer.parseInt(f.getAttribute("num"));
final String typedef = f.getAttribute("type");
if ("exclude".equals(typedef)) {
m.setField(num, null);
} else {
IsoType itype = IsoType.valueOf(typedef);
int length = 0;
if (f.getAttribute("length").length() > 0) {
length = Integer.parseInt(f.getAttribute("length"));
}
String v = f.getChildNodes().item(0).getNodeValue();
CustomField<Object> _cf = mfact.getCustomField(num);
m.setValue(num, _cf == null ? v : _cf.decodeField(v), _cf, itype, length);
}
}
mfact.addMessageTemplate(m);
}
}
}
protected static <T extends IsoMessage> FieldParseInfo getParser(
Element f, MessageFactory<T> mfact) throws IOException {
IsoType itype = IsoType.valueOf(f.getAttribute("type"));
int length = 0;
if (f.getAttribute("length").length() > 0) {
length = Integer.parseInt(f.getAttribute("length"));
}
FieldParseInfo fpi = FieldParseInfo.getInstance(itype, length, mfact.getCharacterEncoding());
NodeList subs = f.getElementsByTagName("field");
if (subs != null && subs.getLength() > 0) {
final CompositeField combo = new CompositeField();
for (int i=0; i<subs.getLength(); i++) {
Element sf = (Element)subs.item(i);
if (sf.getParentNode()==f) {
combo.addParser(getParser(sf, mfact));
}
}
fpi.setDecoder(combo);
}
return fpi;
}
protected static <T extends IsoMessage> void parseGuides(
final NodeList nodes, final MessageFactory<T> mfact) throws IOException {
ArrayList<Element> subs = null;
HashMap<Integer,HashMap<Integer,FieldParseInfo>> guides = new HashMap<Integer, HashMap<Integer, FieldParseInfo>>();
for (int i = 0; i < nodes.getLength(); i++) {
Element elem = (Element)nodes.item(i);
int type = parseType(elem.getAttribute("type"));
if (type == -1) {
throw new IOException("Invalid ISO8583 type for parse guide: " + elem.getAttribute("type"));
}
if (elem.getAttribute("extends") != null && !elem.getAttribute("extends").isEmpty()) {
if (subs == null) {
subs = new ArrayList<Element>(nodes.getLength()-i);
}
subs.add(elem);
continue;
}
HashMap<Integer, FieldParseInfo> parseMap = new HashMap<Integer, FieldParseInfo>();
NodeList fields = elem.getElementsByTagName("field");
for (int j = 0; j < fields.getLength(); j++) {
Element f = (Element)fields.item(j);
if (f.getParentNode()==elem) {
int num = Integer.parseInt(f.getAttribute("num"));
parseMap.put(num, getParser(f, mfact));
}
}
mfact.setParseMap(type, parseMap);
guides.put(type, parseMap);
}
if (subs != null) {
for (Element elem : subs) {
int type = parseType(elem.getAttribute("type"));
int ref = parseType(elem.getAttribute("extends"));
if (ref == -1) {
throw new IllegalArgumentException("Message template "
+ elem.getAttribute("type") + " extends invalid template "
+ elem.getAttribute("extends"));
}
HashMap<Integer,FieldParseInfo> parent = guides.get(ref);
if (parent == null) {
throw new IllegalArgumentException("Parsing guide "
+ elem.getAttribute("type") + " extends nonexistent guide "
+ elem.getAttribute("extends"));
}
HashMap<Integer,FieldParseInfo> child = new HashMap<Integer, FieldParseInfo>();
child.putAll(parent);
NodeList fields = elem.getElementsByTagName("field");
for (int j = 0; j < fields.getLength(); j++) {
Element f = (Element)fields.item(j);
int num = Integer.parseInt(f.getAttribute("num"));
String typedef = f.getAttribute("type");
if ("exclude".equals(typedef)) {
child.remove(num);
} else {
child.put(num, getParser(f, mfact));
}
}
mfact.setParseMap(type, child);
}
}
}
/** Reads the XML from the stream and configures the message factory with its values.
* @param mfact The message factory to be configured with the values read from the XML.
* @param stream The InputStream containing the XML configuration. */
@@ -120,7 +340,7 @@ public class ConfigParser {
@Override
public InputSource resolveEntity(String publicId, String systemId)
throws SAXException, IOException {
if (systemId.indexOf("j8583.dtd") >= 0) {
if (systemId.contains("j8583.dtd")) {
URL dtd = getClass().getResource("j8583.dtd");
if (dtd == null) {
log.warn("Cannot find j8583.dtd in classpath. j8583 config files will not be validated.");
@@ -141,76 +361,10 @@ public class ConfigParser {
}
final Element root = doc.getDocumentElement();
//Read the ISO headers
NodeList nodes = root.getElementsByTagName("header");
Element elem = null;
for (int i = 0; i < nodes.getLength(); i++) {
elem = (Element)nodes.item(i);
int type = parseType(elem.getAttribute("type"));
if (type == -1) {
throw new IOException("Invalid type for ISO8583 header: " + elem.getAttribute("type"));
}
if (elem.getChildNodes() == null || elem.getChildNodes().getLength() == 0) {
throw new IOException("Invalid ISO8583 header element");
}
String header = elem.getChildNodes().item(0).getNodeValue();
if (log.isTraceEnabled()) {
log.trace("Adding ISO8583 header for type {}: {}", elem.getAttribute("type"), header);
}
mfact.setIsoHeader(type, header);
}
//Read the message templates
nodes = root.getElementsByTagName("template");
for (int i = 0; i < nodes.getLength(); i++) {
elem = (Element)nodes.item(i);
int type = parseType(elem.getAttribute("type"));
if (type == -1) {
throw new IOException("Invalid ISO8583 type for template: " + elem.getAttribute("type"));
}
NodeList fields = elem.getElementsByTagName("field");
@SuppressWarnings("unchecked")
T m = (T)new IsoMessage();
m.setType(type);
m.setCharacterEncoding(mfact.getCharacterEncoding());
for (int j = 0; j < fields.getLength(); j++) {
Element f = (Element)fields.item(j);
int num = Integer.parseInt(f.getAttribute("num"));
IsoType itype = IsoType.valueOf(f.getAttribute("type"));
int length = 0;
if (f.getAttribute("length").length() > 0) {
length = Integer.parseInt(f.getAttribute("length"));
}
String v = f.getChildNodes().item(0).getNodeValue();
CustomField<Object> _cf = mfact.getCustomField(num);
m.setValue(num, _cf == null ? v : _cf.decodeField(v), _cf, itype, length);
}
mfact.addMessageTemplate(m);
}
parseHeaders(root.getElementsByTagName("header"), mfact);
parseTemplates(root.getElementsByTagName("template"), mfact);
//Read the parsing guides
nodes = root.getElementsByTagName("parse");
for (int i = 0; i < nodes.getLength(); i++) {
elem = (Element)nodes.item(i);
int type = parseType(elem.getAttribute("type"));
if (type == -1) {
throw new IOException("Invalid ISO8583 type for parse guide: " + elem.getAttribute("type"));
}
NodeList fields = elem.getElementsByTagName("field");
HashMap<Integer, FieldParseInfo> parseMap = new HashMap<Integer, FieldParseInfo>();
for (int j = 0; j < fields.getLength(); j++) {
Element f = (Element)fields.item(j);
int num = Integer.parseInt(f.getAttribute("num"));
IsoType itype = IsoType.valueOf(f.getAttribute("type"));
int length = 0;
if (f.getAttribute("length").length() > 0) {
length = Integer.parseInt(f.getAttribute("length"));
}
parseMap.put(num, FieldParseInfo.getInstance(itype, length, mfact.getCharacterEncoding()));
}
mfact.setParseMap(type, parseMap);
}
parseGuides(root.getElementsByTagName("parse"), mfact);
}
/** Configures a MessageFactory using the default configuration file j8583.xml. This is useful
@@ -18,6 +18,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package com.solab.iso8583.parse;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
@@ -44,7 +45,7 @@ public class Date10ParseInfo extends FieldParseInfo {
@Override
public <T> IsoValue<Date> parse(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException {
throws ParseException, UnsupportedEncodingException {
if (pos < 0) {
throw new ParseException(String.format("Invalid DATE10 field %d position %d",
field, pos), pos);
@@ -57,12 +58,20 @@ public class Date10ParseInfo extends FieldParseInfo {
//we have to use the current date for reference and change what comes in the buffer
Calendar cal = Calendar.getInstance();
//Set the month in the date
cal.set(Calendar.MONTH, ((buf[pos] - 48) * 10) + buf[pos + 1] - 49);
cal.set(Calendar.DATE, ((buf[pos + 2] - 48) * 10) + buf[pos + 3] - 48);
cal.set(Calendar.HOUR_OF_DAY, ((buf[pos + 4] - 48) * 10) + buf[pos + 5] - 48);
cal.set(Calendar.MINUTE, ((buf[pos + 6] - 48) * 10) + buf[pos + 7] - 48);
cal.set(Calendar.SECOND, ((buf[pos + 8] - 48) * 10) + buf[pos + 9] - 48);
cal.set(Calendar.MILLISECOND,0);
if (forceStringDecoding) {
cal.set(Calendar.MONTH, Integer.parseInt(new String(buf, pos, 2, getCharacterEncoding()), 10)-1);
cal.set(Calendar.DATE, Integer.parseInt(new String(buf, pos+2, 2, getCharacterEncoding()), 10));
cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(new String(buf, pos+4, 2, getCharacterEncoding()), 10));
cal.set(Calendar.MINUTE, Integer.parseInt(new String(buf, pos+6, 2, getCharacterEncoding()), 10));
cal.set(Calendar.SECOND, Integer.parseInt(new String(buf, pos+8, 2, getCharacterEncoding()), 10));
} else {
cal.set(Calendar.MONTH, ((buf[pos] - 48) * 10) + buf[pos + 1] - 49);
cal.set(Calendar.DATE, ((buf[pos + 2] - 48) * 10) + buf[pos + 3] - 48);
cal.set(Calendar.HOUR_OF_DAY, ((buf[pos + 4] - 48) * 10) + buf[pos + 5] - 48);
cal.set(Calendar.MINUTE, ((buf[pos + 6] - 48) * 10) + buf[pos + 7] - 48);
cal.set(Calendar.SECOND, ((buf[pos + 8] - 48) * 10) + buf[pos + 9] - 48);
}
cal.set(Calendar.MILLISECOND,0);
adjustWithFutureTolerance(cal);
return new IsoValue<Date>(type, cal.getTime(), null);
}
@@ -18,6 +18,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package com.solab.iso8583.parse;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
@@ -38,7 +39,8 @@ public class Date4ParseInfo extends FieldParseInfo {
@Override
public <T> IsoValue<Date> parse(final int field, final byte[] buf, final int pos,
final CustomField<T> custom) throws ParseException {
final CustomField<T> custom)
throws ParseException, UnsupportedEncodingException {
if (pos < 0) {
throw new ParseException(String.format("Invalid DATE4 field %d position %d",
field, pos), pos);
@@ -53,8 +55,13 @@ public class Date4ParseInfo extends FieldParseInfo {
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
//Set the month in the date
cal.set(Calendar.MONTH, ((buf[pos] - 48) * 10) + buf[pos + 1] - 49);
cal.set(Calendar.DATE, ((buf[pos + 2] - 48) * 10) + buf[pos + 3] - 48);
if (forceStringDecoding) {
cal.set(Calendar.MONTH, Integer.parseInt(new String(buf, pos, 2, getCharacterEncoding()), 10)-1);
cal.set(Calendar.DATE, Integer.parseInt(new String(buf, pos+2, 2, getCharacterEncoding()), 10));
} else {
cal.set(Calendar.MONTH, ((buf[pos] - 48) * 10) + buf[pos + 1] - 49);
cal.set(Calendar.DATE, ((buf[pos + 2] - 48) * 10) + buf[pos + 3] - 48);
}
Date10ParseInfo.adjustWithFutureTolerance(cal);
return new IsoValue<Date>(type, cal.getTime(), null);
}
@@ -18,6 +18,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package com.solab.iso8583.parse;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
@@ -39,7 +40,7 @@ public class DateExpParseInfo extends FieldParseInfo {
@Override
public <T> IsoValue<Date> parse(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException {
throws ParseException, UnsupportedEncodingException {
if (pos < 0) {
throw new ParseException(String.format("Invalid DATE_EXP field %d position %d",
field, pos), pos);
@@ -54,9 +55,15 @@ public class DateExpParseInfo extends FieldParseInfo {
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.DATE, 1);
//Set the month in the date
cal.set(Calendar.YEAR, cal.get(Calendar.YEAR) - (cal.get(Calendar.YEAR) % 100)
+ ((buf[pos] - 48) * 10) + buf[pos + 1] - 48);
cal.set(Calendar.MONTH, ((buf[pos + 2] - 48) * 10) + buf[pos + 3] - 49);
if (forceStringDecoding) {
cal.set(Calendar.YEAR, cal.get(Calendar.YEAR) - (cal.get(Calendar.YEAR) % 100)
+ Integer.parseInt(new String(buf, pos, 2, getCharacterEncoding()), 10));
cal.set(Calendar.MONTH, Integer.parseInt(new String(buf, pos+2, 2, getCharacterEncoding()), 10)-1);
} else {
cal.set(Calendar.YEAR, cal.get(Calendar.YEAR) - (cal.get(Calendar.YEAR) % 100)
+ ((buf[pos] - 48) * 10) + buf[pos + 1] - 48);
cal.set(Calendar.MONTH, ((buf[pos + 2] - 48) * 10) + buf[pos + 3] - 49);
}
return new IsoValue<Date>(type, cal.getTime(), null);
}
@@ -34,6 +34,8 @@ public abstract class FieldParseInfo {
protected IsoType type;
protected int length;
private String encoding = System.getProperty("file.encoding");
protected boolean forceStringDecoding;
private CustomField<?> decoder;
/** Creates a new instance that parses a value of the specified type, with the specified length.
* The length is only useful for ALPHA and NUMERIC types.
@@ -47,6 +49,13 @@ public abstract class FieldParseInfo {
length = len;
}
/** Specified whether length headers for variable-length fields in text mode should
* be decoded using proper string conversion with the character encoding. Default is false,
* which means use the old behavior of decoding as ASCII. */
public void setForceStringDecoding(boolean flag) {
forceStringDecoding = flag;
}
public void setCharacterEncoding(String value) {
encoding = value;
}
@@ -64,6 +73,13 @@ public abstract class FieldParseInfo {
return type;
}
public void setDecoder(CustomField<?> value) {
decoder = value;
}
public CustomField<?> getDecoder() {
return decoder;
}
/** Parses the character data from the buffer and returns the
* IsoValue with the correct data type in it.
* @param field The field index, useful for error reporting.
@@ -119,4 +135,14 @@ public abstract class FieldParseInfo {
return fpi;
}
protected int decodeLength(byte[] buf, int pos, int digits) throws UnsupportedEncodingException {
if (forceStringDecoding) {
return Integer.parseInt(new String(buf, pos, digits, encoding));
} else if (digits == 3) {
return ((buf[pos] - 48) * 100) + ((buf[pos + 1] - 48) * 10) + (buf[pos + 2] - 48);
} else {
return ((buf[pos] - 48) * 10) + (buf[pos + 1] - 48);
}
}
}
@@ -18,6 +18,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package com.solab.iso8583.parse;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import com.solab.iso8583.CustomBinaryField;
@@ -40,7 +41,7 @@ public class LlbinParseInfo extends FieldParseInfo {
@Override
public <T> IsoValue<?> parse(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException {
throws ParseException, UnsupportedEncodingException {
if (pos < 0) {
throw new ParseException(String.format("Invalid LLBIN field %d position %d",
field, pos), pos);
@@ -48,7 +49,7 @@ public class LlbinParseInfo extends FieldParseInfo {
throw new ParseException(String.format("Insufficient LLBIN header field %d",
field), pos);
}
length = ((buf[pos] - 48) * 10) + (buf[pos + 1] - 48);
length = decodeLength(buf, pos, 2);
if (length < 0) {
throw new ParseException(String.format("Invalid LLBIN field %d length %d pos %d",
field, length, pos), pos);
@@ -18,6 +18,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package com.solab.iso8583.parse;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import com.solab.iso8583.CustomBinaryField;
@@ -40,7 +41,7 @@ public class LllbinParseInfo extends FieldParseInfo {
@Override
public <T> IsoValue<?> parse(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException {
throws ParseException, UnsupportedEncodingException {
if (pos < 0) {
throw new ParseException(String.format("Invalid LLLBIN field %d pos %d",
field, pos), pos);
@@ -48,11 +49,7 @@ public class LllbinParseInfo extends FieldParseInfo {
throw new ParseException(String.format("Insufficient LLLBIN header field %d",
field), pos);
}
if (!(Character.isDigit(buf[pos]) && Character.isDigit(buf[pos+1]) && Character.isDigit(buf[pos+2]))) {
throw new ParseException(String.format("Invalid LLLBIN length '%s' field %d pos %d",
new String(buf, pos, 3), field, pos), pos);
}
final int l = ((buf[pos] - 48) * 100) + ((buf[pos + 1] - 48) * 10) + (buf[pos + 2] - 48);
final int l = decodeLength(buf, pos, 3);
if (l < 0) {
throw new ParseException(String.format("Invalid LLLBIN length %d field %d pos %d",
l, field, pos), pos);
@@ -46,11 +46,7 @@ public class LllvarParseInfo extends FieldParseInfo {
throw new ParseException(String.format(
"Insufficient data for LLLVAR header field %d pos %d", field, pos), pos);
}
if (!(Character.isDigit(buf[pos]) && Character.isDigit(buf[pos+1]) && Character.isDigit(buf[pos+2]))) {
throw new ParseException(String.format("Invalid LLLVAR length '%s' field %d pos %d",
new String(buf, pos, 3), field, pos), pos);
}
length = ((buf[pos] - 48) * 100) + ((buf[pos + 1] - 48) * 10) + (buf[pos + 2] - 48);
length = decodeLength(buf, pos, 3);
if (length < 0) {
throw new ParseException(String.format("Invalid LLLVAR length %d field %d pos %d",
length, field, pos), pos);
@@ -44,7 +44,7 @@ public class LlvarParseInfo extends FieldParseInfo {
} else if (pos+2 > buf.length) {
throw new ParseException(String.format("Insufficient data for LLVAR header, pos %d", pos), pos);
}
length = ((buf[pos] - 48) * 10) + (buf[pos + 1] - 48);
length = decodeLength(buf, pos, 2);
if (length < 0) {
throw new ParseException(String.format(
"Invalid LLVAR length %d, field %d pos %d", length, field, pos), pos);
@@ -18,6 +18,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package com.solab.iso8583.parse;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
@@ -40,7 +41,7 @@ public class TimeParseInfo extends FieldParseInfo {
@Override
public <T> IsoValue<Date> parse(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException {
throws ParseException, UnsupportedEncodingException {
if (pos < 0) {
throw new ParseException(String.format("Invalid TIME field %d pos %d",
field, pos), pos);
@@ -49,9 +50,15 @@ public class TimeParseInfo extends FieldParseInfo {
"Insufficient data for TIME field %d, pos %d", field, pos), pos);
}
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, ((buf[pos] - 48) * 10) + buf[pos + 1] - 48);
cal.set(Calendar.MINUTE, ((buf[pos + 2] - 48) * 10) + buf[pos + 3] - 48);
cal.set(Calendar.SECOND, ((buf[pos + 4] - 48) * 10) + buf[pos + 5] - 48);
if (forceStringDecoding) {
cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(new String(buf, pos, 2, getCharacterEncoding()), 10));
cal.set(Calendar.MINUTE, Integer.parseInt(new String(buf, pos+2, 2, getCharacterEncoding()), 10));
cal.set(Calendar.SECOND, Integer.parseInt(new String(buf, pos+4, 2, getCharacterEncoding()), 10));
} else {
cal.set(Calendar.HOUR_OF_DAY, ((buf[pos] - 48) * 10) + buf[pos + 1] - 48);
cal.set(Calendar.MINUTE, ((buf[pos + 2] - 48) * 10) + buf[pos + 3] - 48);
cal.set(Calendar.SECOND, ((buf[pos + 4] - 48) * 10) + buf[pos + 5] - 48);
}
return new IsoValue<Date>(type, cal.getTime(), null);
}
@@ -1,3 +1,21 @@
/*
* j8583 A Java implementation of the ISO8583 protocol
* Copyright (C) 2007 Enrique Zamudio Lopez
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package com.solab.iso8583.util;
import java.math.BigInteger;
@@ -1,9 +1,29 @@
/*
* j8583 A Java implementation of the ISO8583 protocol
* Copyright (C) 2007 Enrique Zamudio Lopez
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package com.solab.iso8583.util;
/** Utility class to perform HEX encoding/decoding of values. */
/** Utility class to perform HEX encoding/decoding of values.
* @author Enrique Zamudio
*/
public final class HexCodec {
static final char[] HEX = new char[]{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
static final char[] HEX = "0123456789ABCDEF".toCharArray();
private HexCodec(){}
@@ -1,3 +1,21 @@
/*
* j8583 A Java implementation of the ISO8583 protocol
* Copyright (C) 2007 Enrique Zamudio Lopez
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package com.solab.iso8583.util;
import com.solab.iso8583.IsoMessage;
@@ -2,14 +2,17 @@
<!ELEMENT header ( #PCDATA ) >
<!ATTLIST header type NMTOKEN #REQUIRED >
<!ATTLIST header ref NMTOKEN #IMPLIED >
<!ELEMENT template ( field* ) >
<!ATTLIST template type NMTOKEN #REQUIRED >
<!ATTLIST template extends NMTOKEN #IMPLIED >
<!ELEMENT parse ( field+ ) >
<!ATTLIST parse type NMTOKEN #REQUIRED >
<!ATTLIST parse extends NMTOKEN #IMPLIED >
<!ELEMENT field ( #PCDATA ) >
<!ELEMENT field ( #PCDATA|field )* >
<!ATTLIST field num NMTOKEN #REQUIRED >
<!ATTLIST field length NMTOKEN #IMPLIED >
<!ATTLIST field type ( ALPHA | NUMERIC | AMOUNT | DATE10 | DATE4 | DATE_EXP | TIME | LLVAR | LLLVAR | BINARY | LLBIN | LLLBIN ) #REQUIRED >
<!ATTLIST field type ( ALPHA | NUMERIC | AMOUNT | DATE10 | DATE4 | DATE_EXP | TIME | LLVAR | LLLVAR | BINARY | LLBIN | LLLBIN | exclude) #REQUIRED >
+78 -2
View File
@@ -20,6 +20,14 @@ XML Configuration
<header type="0400">ISO015000050</header>
+-----
<<New in 1.9:>> You can define a header as a reference to another header:
+-----
<header type="0800" ref="0200" />
+-----
The header for 0800 messages will be the same as the header for 0200 messages.
* <<<template>>> element
Each <<<template>>> element defines a message template, with the message type and the fields that the
@@ -42,6 +50,20 @@ XML Configuration
</template>
+-----
<<New in 1.9:>> You can define a template as extending another template, so that it includes
all the fields from the referenced template as well as any new fields defined in it, as well
as excluding fields from the referenced template:
+-----
<template type="0400" extends="0200">
<field num="90" type="ALPHA" length="42">Bla bla</field>
<field num="102" type="exclude" />
</template>
+-----
In the above example, the template for message type 0400 will include all fields defined in the
template for message type 0200 <except> field 102, and will additionally include field 90.
* <<<parse>>> element
Each <<<parse>>> element defines a parsing template for a message type. It must include all the fields that
@@ -71,10 +93,64 @@ XML Configuration
<field num="60" type="LLLVAR" />
<field num="61" type="LLLVAR" />
<field num="70" type="ALPHA" length="3" />
<field num="90" type="ALPHA" length="42" />
<field num="100" type="LLVAR" />
<field num="102" type="LLVAR" />
<field num="126" type="LLLVAR" />
</parse>
+-----
<<New in 1.9:>> As with message templates, you can define parsing guides that extend other
parsing guides:
+-----
<parse type="0410" extends="0210">
<field num="90" type="ALPHA" length="42" />
<field num="102" type="exclude" />
</parse>
+----
* Composite fields
Another new feature introduced in 1.9 is the CompositeField. This is a CustomField that acts
as a container for several IsoValues, and it can be configured in the parsing guide of
a message type:
+-----
<parse type="0410">
<field num="125" type="LLLVAR">
<field num="1" type="ALPHA" length="5" />
<field num="2" type="LLVAR" />
<field num="3" type="NUMERIC" length="6" />
<field num="4" type="ALPHA" length="2" />
</field>
</parse>
+-----
In the above example, when a message with type 0410 is parsed, the value for field 125 will
be a <<<CompositeField>>> and you can obtain the subfields via <<<getField()>>> or
<<<getObjectValue()>>>. The <<<num>>> attribute of the subfields is ignored; it's just
there to pass the DTD validation.
This means that you can do this via code:
+-----
//Assuming original data for field 125 is "018one 03two123456OK"
CompositeField f = message.getObjectValue(125);
String sub1 = f.getObjectValue(0); //"one "
String sub2 = f.getObjectValue(1); //"two"
String sub3 = f.getObjectValue(2); //"123456"
String sub4 = f.getObjectValue(3); //"OK"
+-----
You can also create a CompositeField, store several subfields inside it, and store it in any
field inside an IsoMessage, specifying the same instance as the CustomField:
+-----
CompositeField f = new CompositeField().addValue(new IsoValue<String>(IsoType.ALPHA, "one", 5))
.addValue(new IsoValue<String>(IsoType.LLVAR, "two"))
.addValue(new IsoValue<Long>(IsoType.NUMERIC, 123l, 6))
.addValue(new IsoValue<String>(IsoType.ALPHA, "OK", 2));
message.setValue(125, f, f, IsoType.LLLVAR, 0);
+-----
When the message is encoded, field 125 will be "<<<018one 03two000123OK>>>".
+75 -2
View File
@@ -19,6 +19,13 @@ Configuración por XML
+-----
<header type="0200">ISO015000050</header>
<header type="0400">ISO015000050</header>
+-----
<<Nuevo en 1.9:>> Se puede definir un elemento <<<header>>> como referencia a otro elemento para que tengan
el mismo valor:
+-----
<header type="0800" ref="0200" />
+-----
* Elemento <<<template>>>
@@ -43,6 +50,19 @@ Configuración por XML
</template>
+-----
<<Nuevo en 1.9:>> Se puede definir una plantilla que extienda otra plantilla, de modo que incluya todos
los campos de la plantilla que extiende, además de poder excluir campos de la plantilla original.
+-----
<template type="0400">
<field num="90" type="ALPHA" length="42">bla bla</field>
<field num="102" type="exclude" />
</template>
+-----
En este ejemplo, la plantilla para mensajes 0400 include todos los campos de los mensajes 0200
<excepto> el campo 102, y además incluye el campo 90.
* Elemento <<<parse>>>
Cada elemento <<<parse>>> define una plantilla de lectura para un tipo de mensaje. Debe incluir todos los
@@ -72,10 +92,63 @@ Configuración por XML
<field num="60" type="LLLVAR" />
<field num="61" type="LLLVAR" />
<field num="70" type="ALPHA" length="3" />
<field num="90" type="ALPHA" length="42" />
<field num="100" type="LLVAR" />
<field num="102" type="LLVAR" />
<field num="126" type="LLLVAR" />
</parse>
+-----
<<Nuevo en 1.9:>> Al igual que las plantillas, las guías de lectura también pueden extender otras guías:
+-----
<parse type="0410">
<field num="90" type="ALPHA" length="42" />
<field num="102" type="exclude" />
</parse>
+-----
* Campos compuestos
Otra característica nueva a partir de la versión 1.9 son los campos compuestos, por medio de la clase
CompositeField, que es una implementación de CustomField que funciona como un contenedor de varios
IsoValues, y puede ser configurado en la guía de lectura de un tipo de mensaje:
+-----
<parse type="0410">
<field num="125" type="LLLVAR">
<field num="1" type="ALPHA" length="5" />
<field num="2" type="LLVAR" />
<field num="3" type="NUMERIC" length="6" />
<field num="4" type="ALPHA" length="2" />
</field>
</parse>
+-----
En este ejemplo, cuando un mensaje tipo 0410 se decodifica, el valor del campo 125 será una instancia
de <<<CompositeField>>> y se pueden obtener sus subcampos con los métodos <<<getField()>>> y
<<<getObjectValue()>>>. El atributo <<<num>>> de los subcampos es ignorado; simplemente se tiene que
incluir para pasar la validación del DTD.
Lo anterior significa que se puede hacer esto con código, una vez que se ha leído el mensaje:
+-----
//Suponiendo que originalmente el campo 125 trae "018one 03two123456OK"
CompositeField f = message.getObjectValue(125);
String sub1 = f.getObjectValue(0); //"one "
String sub2 = f.getObjectValue(1); //"two"
String sub3 = f.getObjectValue(2); //"123456"
String sub4 = f.getObjectValue(3); //"OK"
+-----
También se puede crear un CompositeField, guardar varios valores en él, y luego ponerlo
como valor en cualquier campo de un IsoMessage, usando el mismo objeto como CustomField:
+-----
CompositeField f = new CompositeField().addValue(new IsoValue<String>(IsoType.ALPHA, "one", 5))
.addValue(new IsoValue<String>(IsoType.LLVAR, "two"))
.addValue(new IsoValue<Long>(IsoType.NUMERIC, 123l, 6))
.addValue(new IsoValue<String>(IsoType.ALPHA, "OK", 2));
message.setValue(125, f, f, IsoType.LLLVAR, 0);
+-----
Cuando ese mensaje sea codificado, el campo 125 se verá como "<<<018one 03two000123OK>>>".
+1 -1
View File
@@ -15,7 +15,7 @@
<item name="Download" href="http://sourceforge.net/projects/j8583/files/j8583/"/>
</links>
<menu name="j8583 1.5.7">
<menu name="j8583 1.9.0">
<item name="Introduction" href="index.html"/>
<item name="API docs" href="javadoc/index.html" />
<item name="Beginner's Guide" href="guide.html"/>
+1 -1
View File
@@ -15,7 +15,7 @@
<item name="Descargar" href="http://sourceforge.net/projects/j8583/files/j8583/"/>
</links>
<menu name="j8583 1.5.7">
<menu name="j8583 1.9.0">
<item name="Introducción" href="index.html"/>
<item name="Documentación API" href="../javadoc/index.html" />
<item name="Guía de inicio" href="guide.html"/>
+1 -1
View File
@@ -87,7 +87,7 @@ public class CustomBinCodecs {
}
//Test parsing
tmpl = mfact.parseMessage(buf, 0);
Assert.assertEquals(new Long(1234567890l), tmpl.getObjectValue(2));
Assert.assertEquals(1234567890l, tmpl.getObjectValue(2));
Assert.assertEquals(b29, tmpl.getObjectValue(3));
}
+58
View File
@@ -0,0 +1,58 @@
package j8583;
import com.solab.iso8583.util.Bcd;
import org.junit.Assert;
import org.junit.Test;
/**
* BCD encoding tests.
*
* @author Enrique Zamudio
* Date: 25/11/13 10:43
*/
public class TestBcd {
@Test
public void testEncoding() {
byte[] buf = new byte[2];
buf[0] = 1; buf[1]=1;
Bcd.encode("00", buf);
Assert.assertArrayEquals(new byte[]{0, 1}, buf);
Bcd.encode("79", buf);
Assert.assertArrayEquals(new byte[]{0x79, 1}, buf);
Bcd.encode("80", buf);
Assert.assertArrayEquals(new byte[]{(byte)0x80,1}, buf);
Bcd.encode("99", buf);
Assert.assertArrayEquals(new byte[]{(byte)0x99,1}, buf);
Bcd.encode("100", buf);
Assert.assertArrayEquals(new byte[]{1, 0}, buf);
Bcd.encode("779", buf);
Assert.assertArrayEquals(new byte[]{7, 0x79}, buf);
Bcd.encode("999", buf);
Assert.assertArrayEquals(new byte[]{9,(byte)0x99}, buf);
}
@Test
public void testDecoding() {
byte[] buf = new byte[2];
Assert.assertEquals(0, Bcd.decodeToLong(buf, 0, 1));
Assert.assertEquals(0, Bcd.decodeToLong(buf, 0, 2));
Assert.assertEquals(0, Bcd.decodeToLong(buf, 0, 3));
Assert.assertEquals(0, Bcd.decodeToLong(buf, 0, 4));
buf[0]=0x79;
Assert.assertEquals(79, Bcd.decodeToLong(buf, 0, 2));
buf[0]=(byte)0x80;
Assert.assertEquals(80, Bcd.decodeToLong(buf, 0, 2));
buf[0]=(byte)0x99;
Assert.assertEquals(99, Bcd.decodeToLong(buf, 0, 2));
buf[0]=1;
Assert.assertEquals(100, Bcd.decodeToLong(buf,0,4));
buf[1]=0x79;
Assert.assertEquals(179, Bcd.decodeToLong(buf,0,4));
buf[1]=(byte)0x99;
Assert.assertEquals(199, Bcd.decodeToLong(buf,0,4));
buf[0]=9;
Assert.assertEquals(999, Bcd.decodeToLong(buf,0,4));
}
}
+6
View File
@@ -5,6 +5,7 @@ import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.text.ParseException;
import com.solab.iso8583.IsoType;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -83,6 +84,11 @@ public class TestBinaries {
//Compare values, should be the same
testParsed(bin2);
Assert.assertEquals(bin.getObjectValue(7).toString(), bin2.getObjectValue(7).toString());
//Test the debug string
ascii.setValue(60, "XXX", IsoType.LLVAR, 0);
bin.setValue(60, "XXX", IsoType.LLVAR, 0);
Assert.assertEquals("Debug strings differ", ascii.debugString(), bin.debugString());
Assert.assertTrue("LLVAR fields wrong", ascii.debugString().contains("03XXX"));
}
@Test
+85
View File
@@ -0,0 +1,85 @@
package j8583;
import com.solab.iso8583.IsoType;
import com.solab.iso8583.IsoValue;
import com.solab.iso8583.codecs.CompositeField;
import com.solab.iso8583.parse.AlphaParseInfo;
import com.solab.iso8583.parse.LlvarParseInfo;
import com.solab.iso8583.parse.NumericParseInfo;
import org.junit.Assert;
import org.junit.Test;
/**
* Tests for the CompositeField.
*
* @author Enrique Zamudio
* Date: 25/11/13 17:43
*/
public class TestComposites {
final String textData = "One 03Two00999X";
final byte[] binaryData = new byte[]{'O', 'n', 'e', ' ', ' ', 3, 'T', 'w', 'o',
0, 9, (byte) 0x99, 'X'};
@Test
public void testEncodeText() {
final CompositeField f = new CompositeField();
f.addValue(new IsoValue<String>(IsoType.ALPHA, "One", 5));
f.getValues().get(0).setCharacterEncoding("UTF-8");
Assert.assertEquals("One ", f.encodeField(f));
f.addValue("Two", null, IsoType.LLVAR, 0);
f.getValues().get(1).setCharacterEncoding("UTF-8");
Assert.assertEquals("One 03Two", f.encodeField(f));
f.addValue(999, null, IsoType.NUMERIC, 5);
f.getValues().get(2).setCharacterEncoding("UTF-8");
Assert.assertEquals("One 03Two00999", f.encodeField(f));
f.addValue("X", null, IsoType.ALPHA, 1);
Assert.assertEquals(textData, f.encodeField(f));
}
@Test
public void testEncodeBinary() {
final CompositeField f = new CompositeField()
.addValue(new IsoValue<String>(IsoType.ALPHA, "One", 5));
Assert.assertArrayEquals(new byte[]{'O', 'n', 'e', 32, 32}, f.encodeBinaryField(f));
f.addValue(new IsoValue<String>(IsoType.LLVAR, "Two"));
Assert.assertArrayEquals(new byte[]{'O', 'n', 'e', ' ', ' ', 3, 'T', 'w', 'o'},
f.encodeBinaryField(f));
f.addValue(new IsoValue<Long>(IsoType.NUMERIC, 999l, 5));
f.addValue(new IsoValue<String>(IsoType.ALPHA, "X", 1));
Assert.assertArrayEquals(binaryData, f.encodeBinaryField(f));
}
@Test
public void testDecodeText() {
final CompositeField dec = new CompositeField()
.addParser(new AlphaParseInfo(5))
.addParser(new LlvarParseInfo())
.addParser(new NumericParseInfo(5))
.addParser(new AlphaParseInfo(1));
final CompositeField f = dec.decodeField(textData);
Assert.assertNotNull(f);
Assert.assertEquals(4, f.getValues().size());
Assert.assertEquals("One ", f.getValues().get(0).getValue());
Assert.assertEquals("Two", f.getValues().get(1).getValue());
Assert.assertEquals("00999", f.getValues().get(2).getValue());
Assert.assertEquals("X", f.getValues().get(3).getValue());
}
@Test
public void testDecodeBinary() {
final CompositeField dec = new CompositeField()
.addParser(new AlphaParseInfo(5))
.addParser(new LlvarParseInfo())
.addParser(new NumericParseInfo(5))
.addParser(new AlphaParseInfo(1));
final CompositeField f = dec.decodeBinaryField(binaryData, 0, binaryData.length);
Assert.assertNotNull(f);
Assert.assertEquals(4, f.getValues().size());
Assert.assertEquals("One ", f.getValues().get(0).getValue());
Assert.assertEquals("Two", f.getValues().get(1).getValue());
Assert.assertEquals(999l, f.getValues().get(2).getValue());
Assert.assertEquals("X", f.getValues().get(3).getValue());
}
}
+65
View File
@@ -0,0 +1,65 @@
package j8583;
import com.solab.iso8583.IsoMessage;
import com.solab.iso8583.IsoType;
import com.solab.iso8583.IsoValue;
import com.solab.iso8583.MessageFactory;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.text.ParseException;
/**
* Test certain ConfigParser features.
*
* @author Enrique Zamudio
* Date: 25/11/13 18:41
*/
public class TestConfigParser {
@Test
public void testParser() throws IOException, ParseException {
MessageFactory<IsoMessage> mfact = new MessageFactory<IsoMessage>();
mfact.setConfigPath("config.xml");
//Headers
Assert.assertNotNull(mfact.getIsoHeader(0x800));
Assert.assertNotNull(mfact.getIsoHeader(0x810));
Assert.assertEquals(mfact.getIsoHeader(0x800), mfact.getIsoHeader(0x810));
//Templates
final IsoMessage m200 = mfact.getMessageTemplate(0x200);
Assert.assertNotNull(m200);
final IsoMessage m400 = mfact.getMessageTemplate(0x400);
Assert.assertNotNull(m400);
for (int i=2; i <89; i++) {
IsoValue<?> v = m200.getField(i);
if (v == null) {
Assert.assertFalse(m400.hasField(i));
} else {
Assert.assertTrue(m400.hasField(i));
Assert.assertEquals(v, m400.getField(i));
}
}
Assert.assertFalse(m200.hasField(90));
Assert.assertTrue(m400.hasField(90));
Assert.assertTrue(m200.hasField(102));
Assert.assertFalse(m400.hasField(102));
//Parsing guides
final String s800 = "0800201080000000000012345611251125";
final String s810 = "08102010000002000000123456112500";
IsoMessage m = mfact.parseMessage(s800.getBytes(), 0);
Assert.assertNotNull(m);
Assert.assertTrue(m.hasField(3));
Assert.assertTrue(m.hasField(12));
Assert.assertTrue(m.hasField(17));
Assert.assertFalse(m.hasField(39));
m = mfact.parseMessage(s810.getBytes(), 0);
Assert.assertNotNull(m);
Assert.assertTrue(m.hasField(3));
Assert.assertTrue(m.hasField(12));
Assert.assertFalse(m.hasField(17));
Assert.assertTrue(m.hasField(39));
}
}
+2 -14
View File
@@ -6,12 +6,10 @@ import java.text.ParseException;
import java.util.Date;
import java.util.GregorianCalendar;
import com.solab.iso8583.IsoMessage;
import org.junit.*;
import com.solab.iso8583.IsoType;
import com.solab.iso8583.IsoValue;
import com.solab.iso8583.MessageFactory;
import com.solab.iso8583.parse.Date10ParseInfo;
import com.solab.iso8583.parse.Date4ParseInfo;
@@ -21,16 +19,6 @@ import com.solab.iso8583.parse.Date4ParseInfo;
*/
public class TestDates {
private MessageFactory<IsoMessage> mf;
@Before
public void init() throws IOException {
mf = new MessageFactory<IsoMessage>();
mf.setCharacterEncoding("UTF-8");
mf.setCustomField(48, new CustomField48());
mf.setConfigPath("config.xml");
}
@Test
public void testDate4FutureTolerance() throws ParseException, IOException {
GregorianCalendar today = new GregorianCalendar();
@@ -44,7 +32,7 @@ public class TestDates {
Assert.assertEquals(comp.getValue(), today.getTime());
//Now with the binary
ByteArrayOutputStream bout = new ByteArrayOutputStream();
comp.write(bout, true);
comp.write(bout, true, false);
IsoValue<Date> bin = new Date4ParseInfo().parseBinary(0, bout.toByteArray(), 0, null);
Assert.assertEquals(comp.getValue().getTime(), bin.getValue().getTime());
}
@@ -57,7 +45,7 @@ public class TestDates {
assert comp.getValue().after(new Date());
//Now with the binary
ByteArrayOutputStream bout = new ByteArrayOutputStream();
comp.write(bout, true);
comp.write(bout, true, false);
IsoValue<Date> bin = new Date10ParseInfo().parseBinary(0, bout.toByteArray(), 0, null);
Assert.assertEquals(comp.getValue().getTime(), bin.getValue().getTime());
}
+183
View File
@@ -0,0 +1,183 @@
package j8583;
import com.solab.iso8583.IsoMessage;
import com.solab.iso8583.IsoType;
import com.solab.iso8583.IsoValue;
import com.solab.iso8583.MessageFactory;
import com.solab.iso8583.parse.*;
import com.solab.iso8583.util.HexCodec;
import org.junit.Assert;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.Date;
import java.util.HashMap;
/**
* Test for EBCDIC support.
*
* @author Enrique Zamudio
* Date: 13/05/13 18:12
*/
public class TestEbcdic {
private IsoValue<String> llvar = new IsoValue<String>(IsoType.LLVAR, "Testing, testing, 123");
@Test
public void testAscii() throws IOException, ParseException {
llvar.setCharacterEncoding("UTF-8");
final ByteArrayOutputStream bout = new ByteArrayOutputStream();
llvar.write(bout, false, false);
byte[] buf = bout.toByteArray();
Assert.assertEquals(50, buf[0]);
Assert.assertEquals(49, buf[1]);
final LlvarParseInfo parser = new LlvarParseInfo();
parser.setCharacterEncoding("UTF-8");
IsoValue<?> field = parser.parse(1, buf, 0, null);
Assert.assertEquals(llvar.getValue(), field.getValue());
}
@Test
public void testEbcdic() throws IOException, ParseException {
llvar.setCharacterEncoding("Cp1047");
final ByteArrayOutputStream bout = new ByteArrayOutputStream();
llvar.write(bout, false, true);
byte[] buf = bout.toByteArray();
Assert.assertEquals((byte)242, buf[0]);
Assert.assertEquals((byte) 241, buf[1]);
final LlvarParseInfo parser = new LlvarParseInfo();
parser.setCharacterEncoding("Cp1047");
parser.setForceStringDecoding(true);
IsoValue<?> field = parser.parse(1, buf, 0, null);
Assert.assertEquals(llvar.getValue(), field.getValue());
}
@Test
public void testParsers() throws UnsupportedEncodingException, ParseException {
final byte[] stringA = "A".getBytes("Cp1047");
final LllvarParseInfo lllvar = new LllvarParseInfo();
lllvar.setCharacterEncoding("Cp1047");
lllvar.setForceStringDecoding(true);
IsoValue<?> field = lllvar.parse(1, new byte[]{(byte)240, (byte)240, (byte)241, (byte)193}, 0, null);
Assert.assertEquals(new String(stringA, "Cp1047"), field.getValue());
final LllbinParseInfo lllbin = new LllbinParseInfo();
lllbin.setCharacterEncoding("Cp1047");
lllbin.setForceStringDecoding(true);
field = lllbin.parse(1, new byte[]{(byte)240, (byte)240, (byte)242, 67, 49}, 0, null);
Assert.assertArrayEquals(stringA, (byte[]) field.getValue());
final LlbinParseInfo llbin = new LlbinParseInfo();
llbin.setCharacterEncoding("Cp1047");
llbin.setForceStringDecoding(true);
field = llbin.parse(1, new byte[]{(byte)240, (byte)242, 67, 49}, 0, null);
Assert.assertArrayEquals(stringA, (byte[]) field.getValue());
}
@Test
public void testMessageType() throws UnsupportedEncodingException, ParseException {
final IsoMessage msg = new IsoMessage();
msg.setType(0x1100);
msg.setBinaryBitmap(true);
msg.setCharacterEncoding("Cp1047");
final byte[] enc = msg.writeData();
Assert.assertEquals(12, enc.length);
Assert.assertEquals((byte) 241, enc[0]);
Assert.assertEquals((byte) 241, enc[1]);
Assert.assertEquals((byte)240, enc[2]);
Assert.assertEquals((byte) 240, enc[3]);
MessageFactory<IsoMessage> mf = new MessageFactory<IsoMessage>();
HashMap<Integer, FieldParseInfo> pmap = new HashMap<Integer, FieldParseInfo>();
mf.setForceStringEncoding(true);
mf.setUseBinaryBitmap(true);
mf.setCharacterEncoding("Cp1047");
mf.setParseMap(0x1100, pmap);
IsoMessage m2 = mf.parseMessage(enc, 0);
Assert.assertEquals(msg.getType(), m2.getType());
//Now with text bitmap
msg.setBinaryBitmap(false);
msg.setForceStringEncoding(true);
final byte[] enc2 = msg.writeData();
Assert.assertEquals(20, enc2.length);
mf.setUseBinaryBitmap(false);
m2 = mf.parseMessage(enc2, 0);
Assert.assertEquals(msg.getType(), m2.getType());
}
@Test
public void testDate4() throws UnsupportedEncodingException, ParseException {
final Date4ParseInfo parser = new Date4ParseInfo();
parser.setCharacterEncoding("Cp1047");
parser.setForceStringDecoding(true);
final IsoValue<Date> v = parser.parse(1, new byte[]{(byte)240, (byte)241, (byte)242, (byte)245}, 0, null);
Assert.assertEquals("01", String.format("%Tm", v.getValue()));
Assert.assertEquals("25", String.format("%Td", v.getValue()));
}
@Test
public void testDate10() throws UnsupportedEncodingException, ParseException {
final Date10ParseInfo parser = new Date10ParseInfo();
parser.setCharacterEncoding("Cp1047");
parser.setForceStringDecoding(true);
final IsoValue<Date> v = parser.parse(1, new byte[]{(byte)240, (byte)241, (byte)242, (byte)245, (byte)242, (byte)243, (byte)245, (byte)249, (byte)245, (byte)249}, 0, null);
Assert.assertEquals("01", String.format("%Tm", v.getValue()));
Assert.assertEquals("25", String.format("%Td", v.getValue()));
Assert.assertEquals("23:59:59", String.format("%TT", v.getValue()));
}
@Test
public void testDateExp() throws UnsupportedEncodingException, ParseException {
final DateExpParseInfo parser = new DateExpParseInfo();
parser.setCharacterEncoding("Cp1047");
parser.setForceStringDecoding(true);
final IsoValue<Date> v = parser.parse(1, new byte[]{(byte)241, (byte)247, (byte)241, (byte)242}, 0, null);
Assert.assertEquals("12", String.format("%Tm", v.getValue()));
Assert.assertEquals("2017", String.format("%TY", v.getValue()));
}
@Test
public void testTime() throws UnsupportedEncodingException, ParseException {
final TimeParseInfo parser = new TimeParseInfo();
parser.setCharacterEncoding("Cp1047");
parser.setForceStringDecoding(true);
final IsoValue<Date> v = parser.parse(1, new byte[]{(byte)242, (byte)241, (byte)243, (byte)244, (byte)245, (byte)246}, 0, null);
Assert.assertEquals("21:34:56", String.format("%TT", v.getValue()));
}
@Test
public void testAmount() throws UnsupportedEncodingException, ParseException {
final AmountParseInfo parser = new AmountParseInfo();
parser.setCharacterEncoding("Cp1047");
parser.setForceStringDecoding(true);
IsoValue<BigDecimal> v = parser.parse(4, new byte[]{(byte)240, (byte)240, (byte)240, (byte)241, (byte)242, (byte)243, (byte)244, (byte)245, (byte)246, (byte)247, (byte)248, (byte)249}, 0, null);
Assert.assertEquals(new BigDecimal("1234567.89"), v.getValue());
}
@Test
public void testMessage() throws ParseException, UnsupportedEncodingException {
final byte[] trama = HexCodec.hexDecode("f1f8f1f42030010002000000f0f0f0f0f0f0f0f1f5f9f5f5f1f3f0f6f1f2f1f1f2f9f0f8f8f3f1f8f0f0");
MessageFactory<IsoMessage> mfact = new MessageFactory<IsoMessage>();
mfact.setUseBinaryBitmap(true);
HashMap<Integer, FieldParseInfo> pinfo = new HashMap<Integer, FieldParseInfo>();
pinfo.put(3, new AlphaParseInfo(6));
pinfo.put(11, new AlphaParseInfo(6));
pinfo.put(12, new AlphaParseInfo(12));
pinfo.put(24, new AlphaParseInfo(3));
pinfo.put(39, new AlphaParseInfo(3));
mfact.setParseMap(0x1814, pinfo);
mfact.setCharacterEncoding("Cp1047");
mfact.setForceStringEncoding(true);
IsoMessage iso = mfact.parseMessage(trama, 0);
Assert.assertNotNull(iso);
Assert.assertEquals("000000", iso.getObjectValue(3));
Assert.assertEquals("015955", iso.getObjectValue(11));
Assert.assertEquals("130612112908", iso.getObjectValue(12));
Assert.assertEquals("831", iso.getObjectValue(24));
Assert.assertEquals("800", iso.getObjectValue(39));
}
}
+13
View File
@@ -6,6 +6,7 @@ import java.text.ParseException;
import com.solab.iso8583.IsoMessage;
import com.solab.iso8583.IsoValue;
import com.solab.iso8583.codecs.CompositeField;
import com.solab.iso8583.parse.NumericParseInfo;
import org.junit.Assert;
import org.junit.Before;
@@ -107,4 +108,16 @@ public class TestParsing {
Assert.assertEquals(123456, val.getValue().intValue());
}
@Test
public void testComposite() throws ParseException, UnsupportedEncodingException {
IsoMessage m = mf.parseMessage("12000040000000000000016one 03two12345.".getBytes(), 0);
Assert.assertNotNull(m);
CompositeField f = m.getObjectValue(10);
Assert.assertNotNull(f);
Assert.assertEquals(4, f.getValues().size());
Assert.assertEquals("one ", f.getObjectValue(0));
Assert.assertEquals("two", f.getObjectValue(1));
Assert.assertEquals("12345", f.getObjectValue(2));
Assert.assertEquals(".", f.getObjectValue(3));
}
}
+28 -3
View File
@@ -7,10 +7,10 @@
<!-- These are the ISO headers to be prepended to the message types specified -->
<header type="0200">ISO015000050</header>
<header type="0210">ISO015000055</header>
<header type="0400">ISO015000050</header>
<header type="0410">ISO015000055</header>
<header type="0400" ref="0200" />
<header type="0410" ref="0210" />
<header type="0800">ISO015000015</header>
<header type="0810">ISO015000015</header>
<header type="0810" ref="0800"/>
<!-- The client example uses this to create requests -->
<template type="0200">
@@ -37,6 +37,11 @@
<field num="126" type="LLLVAR">...and yet another fixed field.</field>
</template>
<template type="0400" extends="0200">
<field num="90" type="ALPHA" length="42">BLA</field>
<field num="102" type="exclude" />
</template>
<template type="0600">
<field num="4" type="AMOUNT">1234</field>
<field num="11" type="NUMERIC" length="6">123</field>
@@ -116,4 +121,24 @@
<field num="102" type="LLLVAR" />
</parse>
<parse type="0800">
<field num="3" type="ALPHA" length="6">123456</field>
<field num="12" type="DATE4">1231</field>
<field num="17" type="DATE4">1231</field>
</parse>
<parse type="0810" extends="0800">
<field num="17" type="exclude"/>
<field num="39" type="ALPHA" length="2"/>
</parse>
<parse type="1200">
<field num="10" type="LLLVAR">
<field num="1" type="ALPHA" length="5" />
<field num="2" type="LLVAR"/>
<field num="3" type="NUMERIC" length="5" />
<field num="4" type="ALPHA" length="1" />
</field>
</parse>
</j8583-config>