Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e714d3409 | ||
|
|
ce4f150e8b | ||
|
|
5413998bb6 | ||
|
|
d3245ee728 | ||
|
|
a4f7ec8696 | ||
|
|
629db2304c | ||
|
|
c1c6314144 | ||
|
|
8f498d3551 | ||
|
|
38b161c1c6 | ||
|
|
3b1cf15c3e | ||
|
|
5333413e30 | ||
|
|
3d0749687e | ||
|
|
32dc578814 | ||
|
|
ab740f9efa | ||
|
|
9ecc907739 | ||
|
|
bec6340fe5 | ||
|
|
c9e970904f | ||
|
|
eaab64e73f | ||
|
|
81f178f870 | ||
|
|
8d1e8f389c | ||
|
|
8dd44c4ae3 | ||
|
|
42e17fb036 | ||
|
|
80dd7f78ec | ||
|
|
041c058b33 | ||
|
|
c00ef7b88d | ||
|
|
a05373efec | ||
|
|
5c53676cda | ||
|
|
759ec1497b | ||
|
|
d99279ec7e | ||
|
|
515724bd7e | ||
|
|
59365dae25 | ||
|
|
fd2f9e8103 | ||
|
|
07b277c7fa | ||
|
|
3a4840108d | ||
|
|
8edbad8659 | ||
|
|
2c466a76c5 | ||
|
|
df5c1d7bd2 | ||
|
|
16858e2f42 | ||
|
|
cc1fe845ed | ||
|
|
61fded16d2 | ||
|
|
6bdd26fadf | ||
|
|
0a85393b87 | ||
|
|
1ccf3596d8 | ||
|
|
470d492da2 | ||
|
|
a543212d1e | ||
|
|
096faeeda0 | ||
|
|
406eb91d4b | ||
|
|
8f91cb768c | ||
|
|
078eeb6ee5 | ||
|
|
1b38a648f5 | ||
|
|
29357b8dd2 | ||
|
|
1ce67c621c | ||
|
|
1787dfb0d5 | ||
|
|
2b1f9628bf | ||
|
|
2a38ac2491 | ||
|
|
63e89e708b | ||
|
|
a086cad36d | ||
|
|
11e10bc1eb | ||
|
|
e5aa694a45 | ||
|
|
38df434d0a | ||
|
|
89d8fce075 | ||
|
|
06700670ea | ||
|
|
2e521c2b6d | ||
|
|
160f780309 |
+1
-1
@@ -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
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 >
|
||||
|
||||
@@ -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>>>".
|
||||
|
||||
@@ -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
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user