84 Commits
Author SHA1 Message Date
Enrique Zamudio 3c17b05bbc 1.10.2 2014-12-04 10:30:03 -06:00
Enrique Zamudio 3e3fbaa3fa fix #26 2014-12-04 10:27:55 -06:00
Enrique Zamudio ba486239bd fix #27 2014-12-04 10:27:28 -06:00
Enrique Zamudio c57ad2c5ee fix #31 2014-11-28 12:06:07 -06:00
Enrique Zamudio 6b7ec73e92 mark length final for #31 2014-11-28 11:49:50 -06:00
Enrique Zamudio 0f3cdec460 1.10.1 2014-04-10 17:07:33 -05:00
Enrique Zamudio 327041d3c0 fix #24 2014-04-10 17:07:25 -05:00
Enrique Zamudio a8f699343f tests for #24 2014-04-10 17:07:16 -05:00
Enrique Zamudio a09b72e66e junit 4.11 does have assertNotEquals 2014-03-05 17:22:15 -06:00
Enrique Zamudio 1cbda9aaa9 encoding tests 2014-03-05 17:13:27 -06:00
Enrique Zamudio bb1b23b282 moved tests to proper packages 2014-03-05 16:54:32 -06:00
Enrique Zamudio 9c16e3c063 Set version 1.10.0 2013-12-18 18:47:45 -06:00
Enrique Zamudio e497b7e929 tests for #21 2013-12-18 18:40:11 -06:00
Enrique Zamudio 61b90d4489 Use timezone if available when parsing time/date fields #21 2013-12-18 18:39:55 -06:00
Enrique Zamudio 29a9411b9c Timezone setter for date/time parsers 2013-12-18 18:39:18 -06:00
Enrique Zamudio 1d7eadf2c7 new supertype for all date/time parsers #21 2013-12-18 18:38:43 -06:00
Enrique Zamudio 515a632251 tests for #21 2013-12-18 18:13:57 -06:00
Enrique Zamudio ccfedec8a6 format with timezone for #21 2013-12-18 18:13:42 -06:00
Enrique Zamudio 8aa3ec35ad bump to 1.9.1 since we'd already tagged 1.9.0 f**k 2013-11-27 12:25:13 -06:00
Enrique Zamudio 2c3d3e8a6d check if encoding has been set before using it 2013-11-27 12:23:51 -06:00
Enrique Zamudio 3e714d3409 set version to 1.9.0 2013-11-27 12:19:04 -06:00
Enrique Zamudio ce4f150e8b update documentation for new XML config features 2013-11-27 12:18:47 -06:00
Enrique Zamudio 5413998bb6 update version 2013-11-27 12:18:38 -06:00
Enrique Zamudio d3245ee728 test new method in CompositeField 2013-11-27 12:11:14 -06:00
Enrique Zamudio a4f7ec8696 fixes to CompositeField 2013-11-27 12:10:50 -06:00
Enrique Zamudio 629db2304c test for #17 from xml config 2013-11-27 11:04:54 -06:00
Enrique Zamudio c1c6314144 tests for #17 from xml config 2013-11-27 11:04:35 -06:00
Enrique Zamudio 8f498d3551 binary property is gone 2013-11-27 11:04:19 -06:00
Enrique Zamudio 38b161c1c6 Fix #20 use FieldParseInfo's own CustomField if available 2013-11-27 11:03:47 -06:00
Enrique Zamudio 3b1cf15c3e Parse fields with subfields in parse guides for #17 2013-11-27 11:02:58 -06:00
Enrique Zamudio 5333413e30 improvements to CompositeField 2013-11-27 11:02:19 -06:00
Enrique Zamudio 3d0749687e work on #20 optional CustomField in FieldParseInfo 2013-11-27 11:01:24 -06:00
Enrique Zamudio 32dc578814 refactor common FieldParseInfo creation code 2013-11-26 18:58:40 -06:00
Enrique Zamudio ab740f9efa allow subfiels for #17 2013-11-26 18:50:11 -06:00
Enrique Zamudio 9ecc907739 more thorough tests for composite fields 2013-11-26 18:49:35 -06:00
Enrique Zamudio bec6340fe5 fix length calculation 2013-11-26 18:49:21 -06:00
Enrique Zamudio c9e970904f fix #14 implement parsing guides that extend other guides 2013-11-25 19:49:45 -06:00
Enrique Zamudio eaab64e73f tests for #14 2013-11-25 19:49:25 -06:00
Enrique Zamudio 81f178f870 tests for #15 2013-11-25 19:30:07 -06:00
Enrique Zamudio 8d1e8f389c Implement #15 templates that extend other templates 2013-11-25 19:29:45 -06:00
Enrique Zamudio 8dd44c4ae3 accept type "excluded" for extending templates 2013-11-25 19:28:55 -06:00
Enrique Zamudio 42e17fb036 refactor for #14 and #15 2013-11-25 19:05:41 -06:00
Enrique Zamudio 80dd7f78ec work on #14 2013-11-25 18:56:43 -06:00
Enrique Zamudio 041c058b33 work on #15 2013-11-25 18:56:27 -06:00
Enrique Zamudio c00ef7b88d tests for #16 2013-11-25 18:53:06 -06:00
Enrique Zamudio a05373efec tests for #16 2013-11-25 18:52:30 -06:00
Enrique Zamudio 5c53676cda process ref attribute of header element for #16 2013-11-25 18:51:57 -06:00
Enrique Zamudio 759ec1497b add "ref" attribute to "header" element for #16 2013-11-25 18:51:33 -06:00
Enrique Zamudio d99279ec7e tests for #17 2013-11-25 18:26:13 -06:00
Enrique Zamudio 515724bd7e decoding CompositeField for #17 2013-11-25 18:25:49 -06:00
Enrique Zamudio 59365dae25 set jdk 6 2013-11-25 17:56:52 -06:00
Enrique Zamudio fd2f9e8103 work on CompositeField for #17 2013-11-25 17:56:26 -06:00
Enrique Zamudio 07b277c7fa Tests for #17 2013-11-25 17:55:59 -06:00
Enrique Zamudio 3a4840108d more BCD tests 2013-11-25 11:01:06 -06:00
Enrique Zamudio 8edbad8659 BCD tests 2013-11-25 10:55:08 -06:00
Enrique Zamudio 2c466a76c5 Add LGPL notice 2013-07-15 13:53:24 -05:00
Enrique Zamudio df5c1d7bd2 version 1.8.0 2013-06-25 12:56:45 -05:00
Enrique Zamudio 16858e2f42 version 1.8.0 2013-06-25 12:53:15 -05:00
Enrique Zamudio cc1fe845ed Version 1.8.0 2013-06-25 12:39:13 -05:00
Enrique Zamudio 61fded16d2 Fix debugString for variable-length fields 2013-06-25 12:37:41 -05:00
Enrique Zamudio 6bdd26fadf update site version to 1.8.0 2013-06-12 13:18:05 -05:00
Enrique Zamudio 0a85393b87 Simplify test code 2013-06-12 13:17:34 -05:00
Enrique Zamudio 1ccf3596d8 Test parsing a complete message with EBCDIC 2013-06-12 13:17:18 -05:00
Enrique Zamudio 470d492da2 Propagate char encoding and forceStringEncoding after parsing config 2013-06-12 13:16:28 -05:00
Enrique Zamudio a543212d1e debugString uses substrings for bitmap hex chars 2013-06-12 13:10:35 -05:00
Enrique Zamudio 096faeeda0 indexOf -> contains 2013-06-12 13:09:22 -05:00
Enrique Zamudio 406eb91d4b Simplify hex char array creation 2013-06-12 13:08:58 -05:00
Enrique Zamudio 8f91cb768c Test debugString with binary/ascii messages 2013-06-12 10:42:27 -05:00
Enrique Zamudio 078eeb6ee5 Add debugString() method 2013-06-12 10:42:09 -05:00
Enrique Zamudio 1b38a648f5 Set forceStringEncoding property on new messages 2013-06-12 10:41:50 -05:00
Enrique Zamudio 29357b8dd2 set jdk to 7 (with java6 compat) 2013-06-12 10:39:30 -05:00
Enrique Zamudio 1ce67c621c Encode text bitmap in EBCDIC with forceStringEncoding=true #13 2013-05-13 23:13:44 -05:00
Enrique Zamudio 1787dfb0d5 Test EBCDIC bitmap 2013-05-13 23:12:59 -05:00
Enrique Zamudio 2b1f9628bf Parse text bitmap for #13 2013-05-13 20:03:15 -05:00
Enrique Zamudio 2a38ac2491 force radix 10 2013-05-13 19:48:12 -05:00
Enrique Zamudio 63e89e708b add forceStringEncoding property to pass to messages for #13 2013-05-13 19:47:48 -05:00
Enrique Zamudio a086cad36d Test message parsing for #13 2013-05-13 19:47:27 -05:00
Enrique Zamudio 11e10bc1eb Implement #13 in these parsers 2013-05-13 19:34:21 -05:00
Enrique Zamudio e5aa694a45 More tests for #13 2013-05-13 19:34:04 -05:00
Enrique Zamudio 38df434d0a raise version number 2013-05-13 18:59:29 -05:00
Enrique Zamudio 89d8fce075 Test proper encoding of message type for #13 2013-05-13 18:59:09 -05:00
Enrique Zamudio 06700670ea update API calls 2013-05-13 18:55:01 -05:00
Enrique Zamudio 2e521c2b6d Implement #13 2013-05-13 18:54:44 -05:00
Enrique Zamudio 160f780309 Tests for #13 2013-05-13 18:53:20 -05:00
46 changed files with 1761 additions and 399 deletions
+5 -5
View File
@@ -1,7 +1,7 @@
apply plugin:'java'
apply plugin: 'maven'
defaultTasks 'build'
version='1.7.0'
version='1.10.2'
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
@@ -17,9 +17,9 @@ configurations {
}
dependencies {
compile 'org.slf4j:slf4j-api:1.7.5'
compile 'org.slf4j:slf4j-api:1.7.7'
testCompile 'junit:junit:4.11'
testRuntime 'org.slf4j:slf4j-simple:1.7.5'
testRuntime 'org.slf4j:slf4j-simple:1.7.7'
}
tasks.javadoc.options.use=true
@@ -34,7 +34,7 @@ task sourcesJar(type:Jar) {
classifier='sources'
}
tasks.withType(Compile) { Compile compile ->
tasks.withType(AbstractCompile) { AbstractCompile compile ->
compile.options.debug = true
compile.options.compilerArgs = ['-Xlint:all']
}
@@ -56,7 +56,7 @@ uploadArchives {
beforeDeployment { MavenDeployment deployment ->
signing.signPom(deployment)
}
repository(url:'http://oss.sonatype.org/service/local/staging/deploy/maven2/') {
repository(url:'https://oss.sonatype.org/service/local/staging/deploy/maven2/') {
authentication(userName:nexusUsername, password:nexusPassword)
}
pom.project {
+2 -2
View File
@@ -98,8 +98,8 @@
</option>
</component>
<component name="GradleSettings">
<option name="gradleHome" value="$PROJECT_DIR$/../../../gradle-1.4" />
<option name="preferLocalInstallationToWrapper" value="true" />
<option name="gradleHome" value="$PROJECT_DIR$/../../../gradle-1.8" />
<option name="linkedProjectPath" value="" />
</component>
<component name="GradleUISettings2">
<setting name="root" />
+1 -1
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>net.sf.j8583</groupId>
<artifactId>j8583</artifactId>
<version>1.5.8-SNAPSHOT</version>
<version>1.9.1</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;
/**
+94 -17
View File
@@ -51,6 +51,7 @@ public class IsoMessage {
/** Flag to enforce secondary bitmap even if empty. */
private boolean forceb2;
private boolean binBitmap;
private boolean forceStringEncoding;
private String encoding = System.getProperty("file.encoding");
/** Creates a new empty message with no values set. */
@@ -96,7 +97,15 @@ public class IsoMessage {
return encoding;
}
/** Sets the string to be sent as ISO header, that is, after the length header but before the message type.
/** Specified whether the variable-length fields should encode their length
* headers using string conversion with the proper character encoding. Default
* is false, which is the old behavior (encoding as ASCII). This is only useful
* for text format. */
public void setForceStringEncoding(boolean flag) {
forceStringEncoding = flag;
}
/** Sets the string to be sent as ISO header, that is, after the length header but before the message type.
* This is useful in case an application needs some custom data in the ISO header of each message (very rare). */
public void setIsoHeader(String value) {
isoHeader = value;
@@ -309,6 +318,26 @@ public class IsoMessage {
return buf;
}
/** Creates a BitSet for the bitmap. */
protected BitSet createBitmapBitSet() {
BitSet bs = new BitSet(forceb2 ? 128 : 64);
for (int i = 2 ; i < 129; i++) {
if (fields[i] != null) {
bs.set(i - 1);
}
}
if (forceb2) {
bs.set(0);
} else if (bs.length() > 64) {
//Extend to 128 if needed
BitSet b2 = new BitSet(128);
b2.or(bs);
bs = b2;
bs.set(0);
}
return bs;
}
/** This calls writeInternal(), allowing applications to get the byte buffer containing the
* message data, without the length header. */
public byte[] writeData() {
@@ -333,21 +362,7 @@ public class IsoMessage {
}
//Bitmap
BitSet bs = new BitSet(forceb2 ? 128 : 64);
for (int i = 2 ; i < 129; i++) {
if (fields[i] != null) {
bs.set(i - 1);
}
}
if (forceb2) {
bs.set(0);
} else if (bs.length() > 64) {
//Extend to 128 if needed
BitSet b2 = new BitSet(128);
b2.or(bs);
bs = b2;
bs.set(0);
}
BitSet bs = createBitmapBitSet();
//Write bitmap to stream
if (binary || binBitmap) {
int pos = 128;
@@ -364,6 +379,11 @@ public class IsoMessage {
}
}
} else {
ByteArrayOutputStream bout2 = null;
if (forceStringEncoding) {
bout2 = bout;
bout = new ByteArrayOutputStream();
}
int pos = 0;
int lim = bs.size() / 4;
for (int i = 0; i < lim; i++) {
@@ -378,6 +398,15 @@ public class IsoMessage {
nibble |= 1;
bout.write(HEX[nibble]);
}
if (forceStringEncoding) {
final String _hb = new String(bout.toByteArray());
bout = bout2;
try {
bout.write(_hb.getBytes(encoding));
} catch (IOException ignore) {
//never happen
}
}
}
//Fields
@@ -385,7 +414,7 @@ public class IsoMessage {
IsoValue<?> v = fields[i];
if (v != null) {
try {
v.write(bout, binary);
v.write(bout, binary, forceStringEncoding);
} catch (IOException ex) {
//should never happen, writing to a ByteArrayOutputStream
}
@@ -394,6 +423,48 @@ public class IsoMessage {
return bout.toByteArray();
}
/** Returns a string representation of the message, as if it were encoded
* in ASCII with no binary bitmap. */
public String debugString() {
StringBuilder sb = new StringBuilder();
if (isoHeader != null) {
sb.append(isoHeader);
}
sb.append(String.format("%04x", type));
//Bitmap
BitSet bs = createBitmapBitSet();
int pos = 0;
int lim = bs.size() / 4;
for (int i = 0; i < lim; i++) {
int nibble = 0;
if (bs.get(pos++))
nibble |= 8;
if (bs.get(pos++))
nibble |= 4;
if (bs.get(pos++))
nibble |= 2;
if (bs.get(pos++))
nibble |= 1;
sb.append(new String(HEX, nibble, 1));
}
//Fields
for (int i = 2; i < 129; i++) {
IsoValue<?> v = fields[i];
if (v != null) {
String desc = v.toString();
if (v.getType() == IsoType.LLBIN || v.getType() == IsoType.LLVAR) {
sb.append(String.format("%02d", desc.length()));
} else if (v.getType() == IsoType.LLLBIN || v.getType() == IsoType.LLLVAR) {
sb.append(String.format("%03d", desc.length()));
}
sb.append(desc);
}
}
return sb.toString();
}
//These are for Groovy compat
/** Sets the specified value in the specified field, just like {@link #setField(int, IsoValue)}. */
public <T> void putAt(int i, IsoValue<T> v) {
@@ -425,4 +496,10 @@ public class IsoMessage {
}
}
/** Remove the specified fields from the message. */
public void removeFields(int... idx) {
for (int i : idx) {
setField(i, null);
}
}
}
+15 -7
View File
@@ -19,7 +19,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
package com.solab.iso8583;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
/** Defines the possible values types that can be used in the fields.
* Some types required the length of the value to be specified (NUMERIC
@@ -76,17 +78,23 @@ public enum IsoType {
/** Formats a Date if the receiver is DATE10, DATE4, DATE_EXP or TIME; throws an exception
* otherwise. */
public String format(Date value) {
public String format(final Date value, final TimeZone tz) {
final SimpleDateFormat sdf;
if (this == DATE10) {
return String.format("%Tm%<Td%<TH%<TM%<TS", value);
sdf = new SimpleDateFormat("MMddHHmmss");
} else if (this == DATE4) {
return String.format("%Tm%<Td", value);
sdf = new SimpleDateFormat("MMdd");
} else if (this == DATE_EXP) {
return String.format("%Ty%<Tm", value);
sdf = new SimpleDateFormat("yyMM");
} else if (this == TIME) {
return String.format("%TH%<TM%<TS", value);
}
throw new IllegalArgumentException("Cannot format date as " + this);
sdf = new SimpleDateFormat("HHmmss");
} else {
throw new IllegalArgumentException("Cannot format date as " + this);
}
if (tz != null) {
sdf.setTimeZone(tz);
}
return sdf.format(value);
}
/** Formats the string to the given length (length is only useful if type is ALPHA, NUMERIC or BINARY). */
+52 -40
View File
@@ -22,6 +22,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.util.Date;
import java.util.TimeZone;
import com.solab.iso8583.util.Bcd;
import com.solab.iso8583.util.HexCodec;
@@ -42,6 +43,7 @@ public class IsoValue<T> implements Cloneable {
private CustomField<T> encoder;
private int length;
private String encoding;
private TimeZone tz;
public IsoValue(IsoType t, T value) {
this(t, value, null);
@@ -172,6 +174,14 @@ public class IsoValue<T> implements Cloneable {
return encoding;
}
/** Sets the timezone, useful for date fields. */
public void setTimeZone(TimeZone value) {
tz = value;
}
public TimeZone getTimeZone() {
return tz;
}
/** Returns the formatted value as a String. The formatting depends on the type of the
* receiver. */
public String toString() {
@@ -195,7 +205,7 @@ public class IsoValue<T> implements Cloneable {
} else if (type == IsoType.LLVAR || type == IsoType.LLLVAR) {
return encoder == null ? value.toString() : encoder.encodeField(value);
} else if (value instanceof Date) {
return type.format((Date)value);
return type.format((Date)value, tz);
} else if (type == IsoType.BINARY) {
if (value instanceof byte[]) {
final byte[] _v = (byte[])value;
@@ -245,48 +255,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(encoding == null ? lhead.getBytes():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;
@@ -21,14 +21,9 @@ package com.solab.iso8583;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import com.solab.iso8583.parse.DateTimeParseInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -75,8 +70,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 +104,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 +134,7 @@ public class MessageFactory<T extends IsoMessage> {
public void setForceSecondaryBitmap(boolean flag) {
forceb2 = flag;
}
public boolean getForceSecondaryBitmap() {
public boolean isForceSecondaryBitmap() {
return forceb2;
}
@@ -143,12 +164,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 +178,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 +216,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);
@@ -245,6 +270,20 @@ public class MessageFactory<T extends IsoMessage> {
return resp;
}
/** Sets the timezone for the specified FieldParseInfo, if it's indeed for parsing dates. */
public void setTimezoneForParseGuide(int messageType, int field, TimeZone tz) {
Map<Integer, FieldParseInfo> guide = parseMap.get(messageType);
if (guide != null) {
FieldParseInfo fpi = guide.get(field);
if (fpi instanceof DateTimeParseInfo) {
((DateTimeParseInfo) fpi).setTimeZone(tz);
return;
}
}
log.warn("Field {} for message type {} is not for dates, cannot set timezone",
field, messageType);
}
/** Creates a new message instance from the buffer, which must contain a valid ISO8583
* message. If the factory is set to use binary messages then it will try to parse
* a binary message.
@@ -257,11 +296,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 +341,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+20, 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 +432,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 +463,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 +487,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;
@@ -30,13 +31,8 @@ import com.solab.iso8583.IsoValue;
*
* @author Enrique Zamudio
*/
public class Date10ParseInfo extends FieldParseInfo {
public class Date10ParseInfo extends DateTimeParseInfo {
private static final long FUTURE_TOLERANCE;
static {
FUTURE_TOLERANCE = Long.parseLong(System.getProperty("j8583.future.tolerance", "900000"));
}
public Date10ParseInfo() {
super(IsoType.DATE10, 10);
}
@@ -44,7 +40,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 +53,23 @@ 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);
if (tz != null) {
cal.setTimeZone(tz);
}
adjustWithFutureTolerance(cal);
return new IsoValue<Date>(type, cal.getTime(), null);
}
@@ -94,17 +101,11 @@ public class Date10ParseInfo extends FieldParseInfo {
cal.set(Calendar.MINUTE, tens[3]);
cal.set(Calendar.SECOND, tens[4]);
cal.set(Calendar.MILLISECOND,0);
if (tz != null) {
cal.setTimeZone(tz);
}
adjustWithFutureTolerance(cal);
return new IsoValue<Date>(type, cal.getTime(), null);
}
public static void adjustWithFutureTolerance(Calendar cal) {
//We need to handle a small tolerance into the future (a couple of minutes)
long now = System.currentTimeMillis();
long then = cal.getTimeInMillis();
if (then > now && then-now > FUTURE_TOLERANCE) {
cal.add(Calendar.YEAR, -1);
}
}
}
@@ -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;
@@ -30,7 +31,7 @@ import com.solab.iso8583.IsoValue;
*
* @author Enrique Zamudio
*/
public class Date4ParseInfo extends FieldParseInfo {
public class Date4ParseInfo extends DateTimeParseInfo {
public Date4ParseInfo() {
super(IsoType.DATE4, 4);
@@ -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,16 @@ 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);
}
if (tz != null) {
cal.setTimeZone(tz);
}
Date10ParseInfo.adjustWithFutureTolerance(cal);
return new IsoValue<Date>(type, cal.getTime(), null);
}
@@ -80,7 +90,10 @@ public class Date4ParseInfo extends FieldParseInfo {
cal.set(Calendar.MONTH, tens[0] - 1);
cal.set(Calendar.DATE, tens[1]);
cal.set(Calendar.MILLISECOND,0);
Date10ParseInfo.adjustWithFutureTolerance(cal);
if (tz != null) {
cal.setTimeZone(tz);
}
DateTimeParseInfo.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;
@@ -30,7 +31,7 @@ import com.solab.iso8583.IsoValue;
*
* @author Enrique Zamudio
*/
public class DateExpParseInfo extends FieldParseInfo {
public class DateExpParseInfo extends DateTimeParseInfo {
public DateExpParseInfo() {
super(IsoType.DATE_EXP, 4);
@@ -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,18 @@ 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);
}
if (tz != null) {
cal.setTimeZone(tz);
}
return new IsoValue<Date>(type, cal.getTime(), null);
}
@@ -86,6 +96,9 @@ public class DateExpParseInfo extends FieldParseInfo {
cal.set(Calendar.YEAR, cal.get(Calendar.YEAR)
- (cal.get(Calendar.YEAR) % 100) + tens[0]);
cal.set(Calendar.MONTH, tens[1] - 1);
if (tz != null) {
cal.setTimeZone(tz);
}
return new IsoValue<Date>(type, cal.getTime(), null);
}
}
@@ -0,0 +1,43 @@
package com.solab.iso8583.parse;
import com.solab.iso8583.IsoType;
import java.util.Calendar;
import java.util.TimeZone;
/**
* Abstract class for date/time parsers.
*
* @author Enrique Zamudio
* Date: 18/12/13 18:21
*/
public abstract class DateTimeParseInfo extends FieldParseInfo {
protected static final long FUTURE_TOLERANCE;
protected TimeZone tz;
static {
FUTURE_TOLERANCE = Long.parseLong(System.getProperty("j8583.future.tolerance", "900000"));
}
protected DateTimeParseInfo(IsoType type, int length) {
super(type, length);
}
public void setTimeZone(TimeZone value) {
tz = value;
}
public TimeZone getTimeZone() {
return tz;
}
public static void adjustWithFutureTolerance(Calendar cal) {
//We need to handle a small tolerance into the future (a couple of minutes)
long now = System.currentTimeMillis();
long then = cal.getTimeInMillis();
if (then > now && then-now > FUTURE_TOLERANCE) {
cal.add(Calendar.YEAR, -1);
}
}
}
@@ -32,8 +32,10 @@ import com.solab.iso8583.IsoValue;
public abstract class FieldParseInfo {
protected IsoType type;
protected int length;
protected final 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), 10);
} 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,22 +49,23 @@ 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);
if (length < 0) {
final int len = decodeLength(buf, pos, 2);
if (len < 0) {
throw new ParseException(String.format("Invalid LLBIN field %d length %d pos %d",
field, length, pos), pos);
field, len, pos), pos);
}
if (length+pos+2 > buf.length) {
if (len+pos+2 > buf.length) {
throw new ParseException(String.format(
"Insufficient data for LLBIN field %d, pos %d (LEN states '%s')",
field, pos, new String(buf, pos, 2)), pos);
}
byte[] binval = length == 0 ? new byte[0] : HexCodec.hexDecode(new String(buf, pos + 2, length));
byte[] binval = len == 0 ? new byte[0] : HexCodec.hexDecode(
new String(buf, pos + 2, len));
if (custom == null) {
return new IsoValue<byte[]>(type, binval, binval.length, null);
} else if (custom instanceof CustomBinaryField) {
try {
T dec = ((CustomBinaryField<T>)custom).decodeBinaryField(buf, pos + 2, length);
T dec = ((CustomBinaryField<T>)custom).decodeBinaryField(buf, pos + 2, len);
return dec == null ? new IsoValue<byte[]>(type, binval, binval.length, null) :
new IsoValue<T>(type, dec, 0, custom);
} catch (IndexOutOfBoundsException ex) {
@@ -73,7 +75,7 @@ public class LlbinParseInfo extends FieldParseInfo {
}
} else {
try {
T dec = custom.decodeField(new String(buf, pos + 2, length));
T dec = custom.decodeField(new String(buf, pos + 2, len));
return dec == null ? new IsoValue<byte[]>(type, binval, binval.length, null) :
new IsoValue<T>(type, dec, binval.length, custom);
} catch (IndexOutOfBoundsException ex) {
@@ -97,7 +99,7 @@ public class LlbinParseInfo extends FieldParseInfo {
}
final int l = (((buf[pos] & 0xf0) >> 4) * 10) + (buf[pos] & 0x0f);
if (l < 0) {
throw new ParseException(String.format("Invalid bin LLBIN length %d pos %d", length, pos), pos);
throw new ParseException(String.format("Invalid bin LLBIN length %d pos %d", l, pos), pos);
}
if (l+pos+1 > buf.length) {
throw new ParseException(String.format(
@@ -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,38 +46,35 @@ 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);
if (length < 0) {
final int len = decodeLength(buf, pos, 3);
if (len < 0) {
throw new ParseException(String.format("Invalid LLLVAR length %d field %d pos %d",
length, field, pos), pos);
} else if (length+pos+3 > buf.length) {
len, field, pos), pos);
} else if (len+pos+3 > buf.length) {
throw new ParseException(String.format("Insufficient data for LLLVAR field %d, pos %d",
field, pos), pos);
}
String _v;
try {
_v = length == 0 ? "" : new String(buf, pos + 3, length, getCharacterEncoding());
_v = len == 0 ? "" : new String(buf, pos + 3, len, getCharacterEncoding());
} catch (IndexOutOfBoundsException ex) {
throw new ParseException(String.format(
"Insufficient data for LLLVAR header, field %d pos %d", field, pos), pos);
}
//This is new: if the String's length is different from the specified length in the buffer,
//there are probably some extended characters. So we create a String from the rest of the buffer,
//and then cut it to the specified length.
if (_v.length() != length) {
_v = new String(buf, pos + 3, buf.length-pos-3, getCharacterEncoding()).substring(0, length);
//This is new: if the String's length is different from the specified length in the
//buffer, there are probably some extended characters. So we create a String from
//the rest of the buffer, and then cut it to the specified length.
if (_v.length() != len) {
_v = new String(buf, pos + 3, buf.length-pos-3,
getCharacterEncoding()).substring(0, len);
}
if (custom == null) {
return new IsoValue<String>(type, _v, length, null);
return new IsoValue<String>(type, _v, len, null);
} else {
T decoded = custom.decodeField(_v);
//If decode fails, return string; otherwise use the decoded object and its codec
return decoded == null ? new IsoValue<String>(type, _v, length, null) :
new IsoValue<T>(type, decoded, length, custom);
return decoded == null ? new IsoValue<String>(type, _v, len, null) :
new IsoValue<T>(type, decoded, len, custom);
}
}
@@ -90,20 +87,22 @@ public class LllvarParseInfo extends FieldParseInfo {
throw new ParseException(String.format(
"Insufficient data for bin LLLVAR header, field %d pos %d", field, pos), pos);
}
length = ((buf[pos] & 0x0f) * 100) + (((buf[pos + 1] & 0xf0) >> 4) * 10) + (buf[pos + 1] & 0x0f);
if (length < 0) {
final int len = ((buf[pos] & 0x0f) * 100) + (((buf[pos + 1] & 0xf0) >> 4) * 10) + (buf[pos + 1] & 0x0f);
if (len < 0) {
throw new ParseException(String.format(
"Invalid bin LLLVAR length %d, field %d pos %d", length, field, pos), pos);
} else if (length+pos+2 > buf.length) {
"Invalid bin LLLVAR length %d, field %d pos %d", len, field, pos), pos);
} else if (len+pos+2 > buf.length) {
throw new ParseException(String.format(
"Insufficient data for bin LLLVAR field %d, pos %d", field, pos), pos);
}
if (custom == null) {
return new IsoValue<String>(type, new String(buf, pos + 2, length, getCharacterEncoding()), null);
return new IsoValue<String>(type, new String(buf, pos + 2, len, getCharacterEncoding()), null);
} else {
IsoValue<T> v = new IsoValue<T>(type, custom.decodeField(new String(buf, pos + 2, length, getCharacterEncoding())), custom);
IsoValue<T> v = new IsoValue<T>(type, custom.decodeField(
new String(buf, pos + 2, len, getCharacterEncoding())), custom);
if (v.getValue() == null) {
return new IsoValue<String>(type, new String(buf, pos + 2, length, getCharacterEncoding()), null);
return new IsoValue<String>(type,
new String(buf, pos + 2, len, getCharacterEncoding()), null);
}
return v;
}
@@ -40,37 +40,41 @@ public class LlvarParseInfo extends FieldParseInfo {
final int pos, final CustomField<T> custom)
throws ParseException, UnsupportedEncodingException {
if (pos < 0) {
throw new ParseException(String.format("Invalid LLVAR field %d %d", field, pos), pos);
} 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);
if (length < 0) {
throw new ParseException(String.format(
"Invalid LLVAR length %d, field %d pos %d", length, field, pos), pos);
} else if (length+pos+2 > buf.length) {
"Invalid LLVAR field %d %d", field, pos), pos);
} else if (pos+2 > buf.length) {
throw new ParseException(String.format(
"Insufficient data for LLVAR header, pos %d", pos), pos);
}
final int len = decodeLength(buf, pos, 2);
if (len < 0) {
throw new ParseException(String.format(
"Invalid LLVAR length %d, field %d pos %d", len, field, pos), pos);
} else if (len+pos+2 > buf.length) {
throw new ParseException(String.format(
"Insufficient data for LLVAR field %d, pos %d", field, pos), pos);
}
String _v;
try {
_v = length == 0 ? "" : new String(buf, pos + 2, length, getCharacterEncoding());
_v = len == 0 ? "" : new String(buf, pos + 2, len, getCharacterEncoding());
} catch (IndexOutOfBoundsException ex) {
throw new ParseException(String.format(
"Insufficient data for LLVAR header, field %d pos %d", field, pos), pos);
}
//This is new: if the String's length is different from the specified length in the buffer,
//there are probably some extended characters. So we create a String from the rest of the buffer,
//and then cut it to the specified length.
if (_v.length() != length) {
_v = new String(buf, pos + 2, buf.length-pos-2, getCharacterEncoding()).substring(0, length);
//This is new: if the String's length is different from the specified
// length in the buffer, there are probably some extended characters.
// So we create a String from the rest of the buffer, and then cut it to
// the specified length.
if (_v.length() != len) {
_v = new String(buf, pos + 2, buf.length-pos-2,
getCharacterEncoding()).substring(0, len);
}
if (custom == null) {
return new IsoValue<String>(type, _v, length, null);
return new IsoValue<String>(type, _v, len, null);
} else {
T dec = custom.decodeField(_v);
return dec == null ? new IsoValue<String>(type, _v, length, null) :
new IsoValue<T>(type, dec, length, custom);
return dec == null ? new IsoValue<String>(type, _v, len, null) :
new IsoValue<T>(type, dec, len, custom);
}
}
@@ -83,22 +87,25 @@ public class LlvarParseInfo extends FieldParseInfo {
field, pos), pos);
} else if (pos+1 > buf.length) {
throw new ParseException(String.format(
"Insufficient data for bin LLVAR header, field %d pos %d", field, pos), pos);
"Insufficient data for bin LLVAR header, field %d pos %d",
field, pos), pos);
}
length = (((buf[pos] & 0xf0) >> 4) * 10) + (buf[pos] & 0x0f);
if (length < 0) {
final int len = (((buf[pos] & 0xf0) >> 4) * 10) + (buf[pos] & 0x0f);
if (len < 0) {
throw new ParseException(String.format(
"Invalid bin LLVAR length %d, field %d pos %d", length, field, pos), pos);
"Invalid bin LLVAR length %d, field %d pos %d", len, field, pos), pos);
}
if (length+pos+1 > buf.length) {
if (len+pos+1 > buf.length) {
throw new ParseException(String.format(
"Insufficient data for bin LLVAR field %d, pos %d", field, pos), pos);
}
if (custom == null) {
return new IsoValue<String>(type, new String(buf, pos + 1, length, getCharacterEncoding()), null);
return new IsoValue<String>(type, new String(buf, pos + 1, len,
getCharacterEncoding()), null);
} else {
T dec = custom.decodeField(new String(buf, pos + 1, length, getCharacterEncoding()));
return dec == null ? new IsoValue<String>(type, new String(buf, pos + 1, length, getCharacterEncoding()), null) :
T dec = custom.decodeField(new String(buf, pos + 1, len, getCharacterEncoding()));
return dec == null ? new IsoValue<String>(type,
new String(buf, pos + 1, len, getCharacterEncoding()), null) :
new IsoValue<T>(type, dec, custom);
}
}
@@ -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;
@@ -30,7 +31,7 @@ import com.solab.iso8583.IsoValue;
*
* @author Enrique Zamudio
*/
public class TimeParseInfo extends FieldParseInfo {
public class TimeParseInfo extends DateTimeParseInfo {
public TimeParseInfo() {
@@ -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,18 @@ 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);
}
if (tz != null) {
cal.setTimeZone(tz);
}
return new IsoValue<Date>(type, cal.getTime(), null);
}
@@ -75,6 +85,9 @@ public class TimeParseInfo extends FieldParseInfo {
cal.set(Calendar.HOUR_OF_DAY, tens[0]);
cal.set(Calendar.MINUTE, tens[1]);
cal.set(Calendar.SECOND, tens[2]);
if (tz != null) {
cal.setTimeZone(tz);
}
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+ ) >
<!ELEMENT parse ( field* ) >
<!ATTLIST parse type NMTOKEN #REQUIRED >
<!ATTLIST parse extends NMTOKEN #IMPLIED >
<!ELEMENT field ( #PCDATA ) >
<!ELEMENT field ( #PCDATA|field )* >
<!ATTLIST field num NMTOKEN #REQUIRED >
<!ATTLIST field length NMTOKEN #IMPLIED >
<!ATTLIST field type ( ALPHA | NUMERIC | AMOUNT | DATE10 | DATE4 | DATE_EXP | TIME | LLVAR | LLLVAR | BINARY | LLBIN | LLLBIN ) #REQUIRED >
<!ATTLIST field type ( ALPHA | NUMERIC | AMOUNT | DATE10 | DATE4 | DATE_EXP | TIME | LLVAR | LLLVAR | BINARY | LLBIN | LLLBIN | exclude) #REQUIRED >
+78 -2
View File
@@ -20,6 +20,14 @@ XML Configuration
<header type="0400">ISO015000050</header>
+-----
<<New in 1.9:>> You can define a header as a reference to another header:
+-----
<header type="0800" ref="0200" />
+-----
The header for 0800 messages will be the same as the header for 0200 messages.
* <<<template>>> element
Each <<<template>>> element defines a message template, with the message type and the fields that the
@@ -42,6 +50,20 @@ XML Configuration
</template>
+-----
<<New in 1.9:>> You can define a template as extending another template, so that it includes
all the fields from the referenced template as well as any new fields defined in it, as well
as excluding fields from the referenced template:
+-----
<template type="0400" extends="0200">
<field num="90" type="ALPHA" length="42">Bla bla</field>
<field num="102" type="exclude" />
</template>
+-----
In the above example, the template for message type 0400 will include all fields defined in the
template for message type 0200 <except> field 102, and will additionally include field 90.
* <<<parse>>> element
Each <<<parse>>> element defines a parsing template for a message type. It must include all the fields that
@@ -71,10 +93,64 @@ XML Configuration
<field num="60" type="LLLVAR" />
<field num="61" type="LLLVAR" />
<field num="70" type="ALPHA" length="3" />
<field num="90" type="ALPHA" length="42" />
<field num="100" type="LLVAR" />
<field num="102" type="LLVAR" />
<field num="126" type="LLLVAR" />
</parse>
+-----
<<New in 1.9:>> As with message templates, you can define parsing guides that extend other
parsing guides:
+-----
<parse type="0410" extends="0210">
<field num="90" type="ALPHA" length="42" />
<field num="102" type="exclude" />
</parse>
+----
* Composite fields
Another new feature introduced in 1.9 is the CompositeField. This is a CustomField that acts
as a container for several IsoValues, and it can be configured in the parsing guide of
a message type:
+-----
<parse type="0410">
<field num="125" type="LLLVAR">
<field num="1" type="ALPHA" length="5" />
<field num="2" type="LLVAR" />
<field num="3" type="NUMERIC" length="6" />
<field num="4" type="ALPHA" length="2" />
</field>
</parse>
+-----
In the above example, when a message with type 0410 is parsed, the value for field 125 will
be a <<<CompositeField>>> and you can obtain the subfields via <<<getField()>>> or
<<<getObjectValue()>>>. The <<<num>>> attribute of the subfields is ignored; it's just
there to pass the DTD validation.
This means that you can do this via code:
+-----
//Assuming original data for field 125 is "018one 03two123456OK"
CompositeField f = message.getObjectValue(125);
String sub1 = f.getObjectValue(0); //"one "
String sub2 = f.getObjectValue(1); //"two"
String sub3 = f.getObjectValue(2); //"123456"
String sub4 = f.getObjectValue(3); //"OK"
+-----
You can also create a CompositeField, store several subfields inside it, and store it in any
field inside an IsoMessage, specifying the same instance as the CustomField:
+-----
CompositeField f = new CompositeField().addValue(new IsoValue<String>(IsoType.ALPHA, "one", 5))
.addValue(new IsoValue<String>(IsoType.LLVAR, "two"))
.addValue(new IsoValue<Long>(IsoType.NUMERIC, 123l, 6))
.addValue(new IsoValue<String>(IsoType.ALPHA, "OK", 2));
message.setValue(125, f, f, IsoType.LLLVAR, 0);
+-----
When the message is encoded, field 125 will be "<<<018one 03two000123OK>>>".
+75 -2
View File
@@ -19,6 +19,13 @@ Configuración por XML
+-----
<header type="0200">ISO015000050</header>
<header type="0400">ISO015000050</header>
+-----
<<Nuevo en 1.9:>> Se puede definir un elemento <<<header>>> como referencia a otro elemento para que tengan
el mismo valor:
+-----
<header type="0800" ref="0200" />
+-----
* Elemento <<<template>>>
@@ -43,6 +50,19 @@ Configuración por XML
</template>
+-----
<<Nuevo en 1.9:>> Se puede definir una plantilla que extienda otra plantilla, de modo que incluya todos
los campos de la plantilla que extiende, además de poder excluir campos de la plantilla original.
+-----
<template type="0400">
<field num="90" type="ALPHA" length="42">bla bla</field>
<field num="102" type="exclude" />
</template>
+-----
En este ejemplo, la plantilla para mensajes 0400 include todos los campos de los mensajes 0200
<excepto> el campo 102, y además incluye el campo 90.
* Elemento <<<parse>>>
Cada elemento <<<parse>>> define una plantilla de lectura para un tipo de mensaje. Debe incluir todos los
@@ -72,10 +92,63 @@ Configuración por XML
<field num="60" type="LLLVAR" />
<field num="61" type="LLLVAR" />
<field num="70" type="ALPHA" length="3" />
<field num="90" type="ALPHA" length="42" />
<field num="100" type="LLVAR" />
<field num="102" type="LLVAR" />
<field num="126" type="LLLVAR" />
</parse>
+-----
<<Nuevo en 1.9:>> Al igual que las plantillas, las guías de lectura también pueden extender otras guías:
+-----
<parse type="0410">
<field num="90" type="ALPHA" length="42" />
<field num="102" type="exclude" />
</parse>
+-----
* Campos compuestos
Otra característica nueva a partir de la versión 1.9 son los campos compuestos, por medio de la clase
CompositeField, que es una implementación de CustomField que funciona como un contenedor de varios
IsoValues, y puede ser configurado en la guía de lectura de un tipo de mensaje:
+-----
<parse type="0410">
<field num="125" type="LLLVAR">
<field num="1" type="ALPHA" length="5" />
<field num="2" type="LLVAR" />
<field num="3" type="NUMERIC" length="6" />
<field num="4" type="ALPHA" length="2" />
</field>
</parse>
+-----
En este ejemplo, cuando un mensaje tipo 0410 se decodifica, el valor del campo 125 será una instancia
de <<<CompositeField>>> y se pueden obtener sus subcampos con los métodos <<<getField()>>> y
<<<getObjectValue()>>>. El atributo <<<num>>> de los subcampos es ignorado; simplemente se tiene que
incluir para pasar la validación del DTD.
Lo anterior significa que se puede hacer esto con código, una vez que se ha leído el mensaje:
+-----
//Suponiendo que originalmente el campo 125 trae "018one 03two123456OK"
CompositeField f = message.getObjectValue(125);
String sub1 = f.getObjectValue(0); //"one "
String sub2 = f.getObjectValue(1); //"two"
String sub3 = f.getObjectValue(2); //"123456"
String sub4 = f.getObjectValue(3); //"OK"
+-----
También se puede crear un CompositeField, guardar varios valores en él, y luego ponerlo
como valor en cualquier campo de un IsoMessage, usando el mismo objeto como CustomField:
+-----
CompositeField f = new CompositeField().addValue(new IsoValue<String>(IsoType.ALPHA, "one", 5))
.addValue(new IsoValue<String>(IsoType.LLVAR, "two"))
.addValue(new IsoValue<Long>(IsoType.NUMERIC, 123l, 6))
.addValue(new IsoValue<String>(IsoType.ALPHA, "OK", 2));
message.setValue(125, f, f, IsoType.LLLVAR, 0);
+-----
Cuando ese mensaje sea codificado, el campo 125 se verá como "<<<018one 03two000123OK>>>".
+1 -1
View File
@@ -15,7 +15,7 @@
<item name="Download" href="http://sourceforge.net/projects/j8583/files/j8583/"/>
</links>
<menu name="j8583 1.5.7">
<menu name="j8583 1.9.1">
<item name="Introduction" href="index.html"/>
<item name="API docs" href="javadoc/index.html" />
<item name="Beginner's Guide" href="guide.html"/>
+1 -1
View File
@@ -15,7 +15,7 @@
<item name="Descargar" href="http://sourceforge.net/projects/j8583/files/j8583/"/>
</links>
<menu name="j8583 1.5.7">
<menu name="j8583 1.9.1">
<item name="Introducción" href="index.html"/>
<item name="Documentación API" href="../javadoc/index.html" />
<item name="Guía de inicio" href="guide.html"/>
@@ -1,6 +1,4 @@
package j8583;
import com.solab.iso8583.CustomField;
package com.solab.iso8583;
/** This is an example of a custom field codec, which converts between strings and instances of this same class.
* It's used to test the encoding and decoding of custom fields by the message factory.
@@ -1,4 +1,4 @@
package j8583;
package com.solab.iso8583;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@@ -9,8 +9,6 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import com.solab.iso8583.IsoMessage;
import com.solab.iso8583.MessageFactory;
import com.solab.iso8583.util.HexCodec;
/** Test binary message encoding and binary fields. */
@@ -83,6 +81,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,61 @@
package com.solab.iso8583;
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));
}
}
@@ -0,0 +1,179 @@
package com.solab.iso8583;
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));
}
}
@@ -0,0 +1,56 @@
package com.solab.iso8583;
import java.math.BigDecimal;
import java.util.Date;
import java.util.TimeZone;
import org.junit.Assert;
import org.junit.Test;
/** Tests formatting of certain IsoTypes.
*
* @author Enrique Zamudio
*/
public class TestFormats {
private final Date date = new Date(96867296000l);
@Test
public void testDateFormats() {
assert IsoType.DATE10.format(date, null).equals("0125213456");
assert IsoType.DATE4.format(date, null).equals("0125");
assert IsoType.DATE_EXP.format(date, null).equals("7301");
assert IsoType.TIME.format(date, null).equals("213456");
//Now with GMT
TimeZone gmt = TimeZone.getTimeZone("GMT");
Assert.assertEquals("0126033456", IsoType.DATE10.format(date, gmt));
Assert.assertEquals("0126", IsoType.DATE4.format(date, gmt));
Assert.assertEquals("7301", IsoType.DATE_EXP.format(date, gmt));
Assert.assertEquals("033456", IsoType.TIME.format(date, gmt));
//And now with GMT+1
gmt = TimeZone.getTimeZone("GMT+0100");
Assert.assertEquals("0126043456", IsoType.DATE10.format(date, gmt));
Assert.assertEquals("0126", IsoType.DATE4.format(date, gmt));
Assert.assertEquals("7301", IsoType.DATE_EXP.format(date, gmt));
Assert.assertEquals("043456", IsoType.TIME.format(date, gmt));
}
@Test
public void testNumericFormats() {
assert IsoType.NUMERIC.format(123, 6).equals("000123");
assert IsoType.NUMERIC.format("hola", 6).equals("00hola");
assert IsoType.AMOUNT.format(12345, 0).equals("000001234500");
assert IsoType.AMOUNT.format(new BigDecimal("12345.67"), 0).equals("000001234567");
assert IsoType.AMOUNT.format("1234.56", 0).equals("000000123456");
}
@Test
public void testStringFormats() {
assert IsoType.ALPHA.format("hola", 3).equals("hol");
assert IsoType.ALPHA.format("hola", 4).equals("hola");
assert IsoType.ALPHA.format("hola", 6).equals("hola ");
assert IsoType.LLVAR.format("hola", 0).equals("hola");
assert IsoType.LLLVAR.format("hola", 0).equals("hola");
}
}
@@ -1,18 +1,15 @@
package j8583;
package com.solab.iso8583;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.util.Arrays;
import java.util.List;
import com.solab.iso8583.IsoValue;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import com.solab.iso8583.IsoMessage;
import com.solab.iso8583.IsoType;
import com.solab.iso8583.MessageFactory;
/** These are very simple tests for creating and manipulating messages.
*
* @author Enrique Zamudio
@@ -82,7 +79,7 @@ public class TestIsoMessage {
@Test
public void testParsing() throws IOException, ParseException {
InputStream ins = getClass().getResourceAsStream("/parse1.txt");
byte[] buf = new byte[400];
final byte[] buf = new byte[400];
int pos = 0;
while (ins.available() > 0) {
buf[pos++] = (byte)ins.read();
@@ -96,6 +93,15 @@ public class TestIsoMessage {
byte[] b3 = new byte[b2.length];
System.arraycopy(buf, 0, b3, 0, b3.length);
Assert.assertArrayEquals(b3, b2);
//Test it contains the correct fields
final List<Integer> fields = Arrays.asList(3, 4, 7, 11, 12, 13, 15, 17, 32, 35, 37, 38, 39, 41, 43, 49, 60, 61, 100, 102, 126);
testFields(iso, fields);
//Again, but now with forced encoding
mf.setForceStringEncoding(true);
iso = mf.parseMessage(buf, mf.getIsoHeader(0x210).length());
Assert.assertEquals(0x210, iso.getType());
testFields(iso, fields);
}
@Test
@@ -129,4 +135,13 @@ public class TestIsoMessage {
throw new RuntimeException("Update failed!");
}
private void testFields(IsoMessage m, List<Integer> fields) {
for (int i = 2; i < 128; i++) {
if (fields.contains(i)) {
Assert.assertTrue(m.hasField(i));
} else {
Assert.assertFalse(m.hasField(i));
}
}
}
}
@@ -1,7 +1,5 @@
package j8583;
package com.solab.iso8583;
import com.solab.iso8583.IsoMessage;
import com.solab.iso8583.MessageFactory;
import com.solab.iso8583.parse.ConfigParser;
import org.junit.Assert;
import org.junit.Test;
@@ -1,18 +1,19 @@
package j8583;
package com.solab.iso8583;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
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;
import org.junit.Test;
import com.solab.iso8583.MessageFactory;
/** Test that parsing invalid messages is properly handled.
*
* @author Enrique Zamudio
@@ -107,4 +108,44 @@ 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));
}
@Test
public void testDates() throws ParseException, UnsupportedEncodingException {
IsoMessage m = mf.parseMessage("060002000000000000000125213456".getBytes(), 0);
Assert.assertNotNull(m);
Date f = m.getObjectValue(7);
Assert.assertNotNull(f);
GregorianCalendar cal = new GregorianCalendar();
cal.setTime(f);
Assert.assertEquals(Calendar.JANUARY, cal.get(Calendar.MONTH));
Assert.assertEquals(25, cal.get(Calendar.DATE));
Assert.assertEquals(21, cal.get(Calendar.HOUR_OF_DAY));
mf.setTimezoneForParseGuide(0x600, 7, TimeZone.getTimeZone("GMT"));
m = mf.parseMessage("060002000000000000000125213456".getBytes(), 0);
f = m.getObjectValue(7);
cal.setTime(f);
Assert.assertEquals(Calendar.JANUARY, cal.get(Calendar.MONTH));
Assert.assertEquals(25, cal.get(Calendar.DATE));
Assert.assertEquals(15, cal.get(Calendar.HOUR_OF_DAY));
mf.setTimezoneForParseGuide(0x600, 7, TimeZone.getTimeZone("GMT+0100"));
m = mf.parseMessage("060002000000000000000125213456".getBytes(), 0);
f = m.getObjectValue(7);
cal.setTime(f);
Assert.assertEquals(Calendar.JANUARY, cal.get(Calendar.MONTH));
Assert.assertEquals(25, cal.get(Calendar.DATE));
Assert.assertEquals(14, cal.get(Calendar.HOUR_OF_DAY));
}
}
@@ -1,13 +1,10 @@
package j8583;
package com.solab.iso8583.parse;
import com.solab.iso8583.IsoType;
import com.solab.iso8583.MessageFactory;
import com.solab.iso8583.IsoMessage;
import com.solab.iso8583.codecs.BigIntBcdCodec;
import com.solab.iso8583.codecs.LongBcdCodec;
import com.solab.iso8583.parse.FieldParseInfo;
import com.solab.iso8583.parse.LlbinParseInfo;
import com.solab.iso8583.parse.LllbinParseInfo;
import com.solab.iso8583.util.HexCodec;
import org.junit.Assert;
import org.junit.Test;
@@ -87,7 +84,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,82 @@
package com.solab.iso8583.parse;
import com.solab.iso8583.IsoType;
import com.solab.iso8583.IsoValue;
import com.solab.iso8583.codecs.CompositeField;
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());
}
}
@@ -1,4 +1,4 @@
package j8583;
package com.solab.iso8583.parse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -6,14 +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;
/** Test that the dates are formatted and parsed correctly.
*
@@ -21,16 +17,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();
@@ -39,12 +25,12 @@ public class TestDates {
today.set(GregorianCalendar.MINUTE,0);
today.set(GregorianCalendar.SECOND,0);
today.set(GregorianCalendar.MILLISECOND,0);
byte[] buf = IsoType.DATE4.format(soon).getBytes();
byte[] buf = IsoType.DATE4.format(soon, null).getBytes();
IsoValue<Date> comp = new Date4ParseInfo().parse(0, buf, 0, null);
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());
}
@@ -52,12 +38,12 @@ public class TestDates {
@Test
public void testDate10FutureTolerance() throws ParseException, IOException {
Date soon = new Date(System.currentTimeMillis() + 50000);
byte[] buf = IsoType.DATE10.format(soon).getBytes();
byte[] buf = IsoType.DATE10.format(soon, null).getBytes();
IsoValue<Date> comp = new Date10ParseInfo().parse(0, buf, 0, null);
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,31 @@
package com.solab.iso8583.parse;
import com.solab.iso8583.IsoValue;
import org.junit.Assert;
import org.junit.Test;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
/**
* Test parsing of data with different encodings.
*
* @author Enrique Zamudio
* Date: 05/03/14 16:55
*/
public class TestEncoding {
@Test
public void windowsToUtf8() throws UnsupportedEncodingException, ParseException {
final String data = "05ácido";
final byte[] buf = data.getBytes("ISO-8859-1");
final LlvarParseInfo parser = new LlvarParseInfo();
parser.setCharacterEncoding("UTF-8");
IsoValue<?> field = parser.parse(1, buf, 0, null);
Assert.assertNotEquals(field.getValue(), data.substring(2));
parser.setCharacterEncoding("ISO-8859-1");
field = parser.parse(1, buf, 0, null);
Assert.assertEquals(data.substring(2), field.getValue());
}
}
@@ -0,0 +1,57 @@
package com.solab.iso8583.util;
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));
}
}
@@ -1,11 +1,8 @@
package j8583;
package com.solab.iso8583.util;
import org.junit.Assert;
import org.junit.Test;
import com.solab.iso8583.util.HexCodec;
public class TestHexCodec {
public void encodeDecode(String hex) {
-44
View File
@@ -1,44 +0,0 @@
package j8583;
import java.math.BigDecimal;
import java.util.Date;
import org.junit.Test;
import com.solab.iso8583.IsoType;
/** Tests formatting of certain IsoTypes.
*
* @author Enrique Zamudio
*/
public class TestFormats {
private Date date = new Date(96867296000l);
@Test
public void testDateFormats() {
assert IsoType.DATE10.format(date).equals("0125213456");
assert IsoType.DATE4.format(date).equals("0125");
assert IsoType.DATE_EXP.format(date).equals("7301");
assert IsoType.TIME.format(date).equals("213456");
}
@Test
public void testNumericFormats() {
assert IsoType.NUMERIC.format(123, 6).equals("000123");
assert IsoType.NUMERIC.format("hola", 6).equals("00hola");
assert IsoType.AMOUNT.format(12345, 0).equals("000001234500");
assert IsoType.AMOUNT.format(new BigDecimal("12345.67"), 0).equals("000001234567");
assert IsoType.AMOUNT.format("1234.56", 0).equals("000000123456");
}
@Test
public void testStringFormats() {
assert IsoType.ALPHA.format("hola", 3).equals("hol");
assert IsoType.ALPHA.format("hola", 4).equals("hola");
assert IsoType.ALPHA.format("hola", 6).equals("hola ");
assert IsoType.LLVAR.format("hola", 0).equals("hola");
assert IsoType.LLLVAR.format("hola", 0).equals("hola");
}
}
+28 -3
View File
@@ -7,10 +7,10 @@
<!-- These are the ISO headers to be prepended to the message types specified -->
<header type="0200">ISO015000050</header>
<header type="0210">ISO015000055</header>
<header type="0400">ISO015000050</header>
<header type="0410">ISO015000055</header>
<header type="0400" ref="0200" />
<header type="0410" ref="0210" />
<header type="0800">ISO015000015</header>
<header type="0810">ISO015000015</header>
<header type="0810" ref="0800"/>
<!-- The client example uses this to create requests -->
<template type="0200">
@@ -37,6 +37,11 @@
<field num="126" type="LLLVAR">...and yet another fixed field.</field>
</template>
<template type="0400" extends="0200">
<field num="90" type="ALPHA" length="42">BLA</field>
<field num="102" type="exclude" />
</template>
<template type="0600">
<field num="4" type="AMOUNT">1234</field>
<field num="11" type="NUMERIC" length="6">123</field>
@@ -116,4 +121,24 @@
<field num="102" type="LLLVAR" />
</parse>
<parse type="0800">
<field num="3" type="ALPHA" length="6">123456</field>
<field num="12" type="DATE4">1231</field>
<field num="17" type="DATE4">1231</field>
</parse>
<parse type="0810" extends="0800">
<field num="17" type="exclude"/>
<field num="39" type="ALPHA" length="2"/>
</parse>
<parse type="1200">
<field num="10" type="LLLVAR">
<field num="1" type="ALPHA" length="5" />
<field num="2" type="LLVAR"/>
<field num="3" type="NUMERIC" length="5" />
<field num="4" type="ALPHA" length="1" />
</field>
</parse>
</j8583-config>