58 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
45 changed files with 1294 additions and 267 deletions
+5 -5
View File
@@ -1,7 +1,7 @@
apply plugin:'java'
apply plugin: 'maven'
defaultTasks 'build'
version='1.8.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 {
+3 -3
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" />
@@ -271,7 +271,7 @@
<module fileurl="file://$PROJECT_DIR$/j8583.iml" filepath="$PROJECT_DIR$/j8583.iml" />
</modules>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="Java 7" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="Java 6" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="SvnConfiguration" maxAnnotateRevisions="500" myUseAcceleration="nothing" myAutoUpdateAfterCommit="false" cleanupOnStartRun="false">
+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;
/**
@@ -496,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). */
+12 -2
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;
@@ -262,7 +272,7 @@ public class IsoValue<T> implements Cloneable {
} else if (ldiff == 2) {
lhead = "00" + lhead;
}
outs.write(lhead.getBytes(encoding));
outs.write(encoding == null ? lhead.getBytes():lhead.getBytes(encoding));
} else {
//write the length in ASCII
if (digits == 3) {
@@ -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;
@@ -275,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.
@@ -364,7 +373,7 @@ public class MessageFactory<T extends IsoMessage> {
throw new ParseException("Insufficient length for secondary bitmap", minlength);
}
if (forceStringEncoding) {
byte[] _bb = new String(buf, isoHeaderLength+4, 16, encoding).getBytes();
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++) {
@@ -423,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
@@ -450,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;
@@ -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. */
@@ -141,75 +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
@@ -31,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);
}
@@ -72,6 +67,9 @@ public class Date10ParseInfo extends FieldParseInfo {
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);
}
@@ -103,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);
}
}
}
@@ -31,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);
@@ -61,6 +61,9 @@ public class Date4ParseInfo extends FieldParseInfo {
} 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);
@@ -87,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);
}
@@ -31,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);
@@ -63,6 +63,9 @@ public class DateExpParseInfo extends FieldParseInfo {
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);
}
@@ -93,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,9 +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.
@@ -72,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.
@@ -129,7 +137,7 @@ public abstract class FieldParseInfo {
protected int decodeLength(byte[] buf, int pos, int digits) throws UnsupportedEncodingException {
if (forceStringDecoding) {
return Integer.parseInt(new String(buf, pos, digits, encoding));
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 {
@@ -49,22 +49,23 @@ public class LlbinParseInfo extends FieldParseInfo {
throw new ParseException(String.format("Insufficient LLBIN header field %d",
field), pos);
}
length = decodeLength(buf, pos, 2);
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) {
@@ -74,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) {
@@ -98,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(
@@ -46,34 +46,35 @@ public class LllvarParseInfo extends FieldParseInfo {
throw new ParseException(String.format(
"Insufficient data for LLLVAR header field %d pos %d", field, pos), pos);
}
length = decodeLength(buf, pos, 3);
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);
}
}
@@ -86,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 = decodeLength(buf, pos, 2);
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);
}
}
@@ -31,7 +31,7 @@ import com.solab.iso8583.IsoValue;
*
* @author Enrique Zamudio
*/
public class TimeParseInfo extends FieldParseInfo {
public class TimeParseInfo extends DateTimeParseInfo {
public TimeParseInfo() {
@@ -58,6 +58,9 @@ public class TimeParseInfo extends FieldParseInfo {
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);
}
@@ -82,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,6 +1,26 @@
/*
* 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 = "0123456789ABCDEF".toCharArray();
@@ -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.8.0">
<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,17 +1,14 @@
package j8583;
package com.solab.iso8583;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.text.ParseException;
import com.solab.iso8583.IsoType;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import com.solab.iso8583.IsoMessage;
import com.solab.iso8583.MessageFactory;
import com.solab.iso8583.util.HexCodec;
/** Test binary message encoding and binary fields. */
@@ -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));
}
}
@@ -1,9 +1,5 @@
package j8583;
package com.solab.iso8583;
import com.solab.iso8583.IsoMessage;
import com.solab.iso8583.IsoType;
import com.solab.iso8583.IsoValue;
import com.solab.iso8583.MessageFactory;
import com.solab.iso8583.parse.*;
import com.solab.iso8583.util.HexCodec;
import org.junit.Assert;
@@ -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;
@@ -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;
@@ -10,8 +10,6 @@ import org.junit.*;
import com.solab.iso8583.IsoType;
import com.solab.iso8583.IsoValue;
import com.solab.iso8583.parse.Date10ParseInfo;
import com.solab.iso8583.parse.Date4ParseInfo;
/** Test that the dates are formatted and parsed correctly.
*
@@ -27,7 +25,7 @@ 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
@@ -40,7 +38,7 @@ 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
@@ -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>