Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea6896b767 | ||
|
|
992c737997 | ||
|
|
bec09e90fa | ||
|
|
188fcf5f3f | ||
|
|
505c8d89cb | ||
|
|
26f7746aae | ||
|
|
9746b00eca | ||
|
|
73c915ed95 | ||
|
|
b3215747c6 | ||
|
|
9ef99fd676 | ||
|
|
65f2360c51 | ||
|
|
4da4d6f9c9 | ||
|
|
3a32b90414 | ||
|
|
ab1d882476 | ||
|
|
a0c8413171 | ||
|
|
95a09942ff | ||
|
|
e3906fd4bc | ||
|
|
b84bf250b1 | ||
|
|
899154cbad | ||
|
|
09884e2f7d | ||
|
|
dabaac817a | ||
|
|
ea05c71410 | ||
|
|
fa82e83e22 | ||
|
|
450f073879 | ||
|
|
3df3235afa | ||
|
|
6f6652411c | ||
|
|
7608d7108f | ||
|
|
40a8c885db | ||
|
|
fcbc9c9ca5 | ||
|
|
21d25fb38a | ||
|
|
9e152cca33 | ||
|
|
8df997f101 | ||
|
|
3123eb4ea9 | ||
|
|
fa59763812 | ||
|
|
86d4cdb3ee | ||
|
|
6ed0ce0eb7 | ||
|
|
e8d914fe20 | ||
|
|
7d431f1f36 | ||
|
|
87680e7684 | ||
|
|
3f879523bc | ||
|
|
f582159495 | ||
|
|
bbee6601f9 | ||
|
|
e6444ebad3 | ||
|
|
eae946665b | ||
|
|
2ef2690df5 | ||
|
|
002eb48a50 | ||
|
|
d58b4a13a2 | ||
|
|
76aa3a11d3 | ||
|
|
28034fcd62 | ||
|
|
556a050575 | ||
|
|
bfc5c2bedc | ||
|
|
2970805a71 | ||
|
|
d751b1746e | ||
|
|
ecbc894f6b | ||
|
|
7c8931e9f4 | ||
|
|
f251343533 | ||
|
|
dd83ef7b96 | ||
|
|
e278903423 | ||
|
|
fa4006f270 | ||
|
|
1a60183268 | ||
|
|
e12f09430f | ||
|
|
00c046881b | ||
|
|
576dc50b44 | ||
|
|
2db47ed814 | ||
|
|
e310a859cb | ||
|
|
584aaeffc1 | ||
|
|
15a3d03766 | ||
|
|
1787dedfa1 | ||
|
|
143d14f07c | ||
|
|
34c19e26ec | ||
|
|
e74fb1b51c | ||
|
|
d974f6f85b | ||
|
|
eb1c0437d4 | ||
|
|
657b32ab10 | ||
|
|
a7561ce42b |
+7
-7
@@ -1,10 +1,10 @@
|
||||
apply plugin:'java'
|
||||
apply plugin: 'maven'
|
||||
defaultTasks 'build'
|
||||
version='1.10.2'
|
||||
version='1.12.0'
|
||||
description='Java implementation of the ISO 8583 protocol, focused on making the creation, edition and reading of ISO8583 messages as simple and flexible as possible.'
|
||||
sourceCompatibility=6
|
||||
targetCompatibility=6
|
||||
sourceCompatibility=7
|
||||
targetCompatibility=7
|
||||
group='net.sf.j8583'
|
||||
|
||||
repositories {
|
||||
@@ -17,13 +17,13 @@ configurations {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.slf4j:slf4j-api:1.7.7'
|
||||
testCompile 'junit:junit:4.11'
|
||||
testRuntime 'org.slf4j:slf4j-simple:1.7.7'
|
||||
compile 'org.slf4j:slf4j-api:1.7.12'
|
||||
testCompile 'junit:junit:4.12'
|
||||
testRuntime 'org.slf4j:slf4j-simple:1.7.12'
|
||||
}
|
||||
|
||||
tasks.javadoc.options.use=true
|
||||
tasks.javadoc.options.links=['http://download.oracle.com/javase/6/docs/api/', 'http://slf4j.org/apidocs/' ]
|
||||
tasks.javadoc.options.links=['http://docs.oracle.com/javase/7/docs/api/', 'http://slf4j.org/apidocs/' ]
|
||||
|
||||
task javadocJar(type:Jar, dependsOn:'javadoc') {
|
||||
from javadoc.destinationDir
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
@@ -16,5 +17,4 @@
|
||||
<orderEntry type="library" scope="TEST" name="jUnit" level="application" />
|
||||
<orderEntry type="library" scope="RUNTIME" name="slf4j-runtime" level="application" />
|
||||
</component>
|
||||
</module>
|
||||
|
||||
</module>
|
||||
@@ -21,102 +21,34 @@
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
<component name="CopyrightManager" default="">
|
||||
<module2copyright />
|
||||
</component>
|
||||
<component name="CopyrightManager" default="" />
|
||||
<component name="DependencyValidationManager">
|
||||
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
|
||||
</component>
|
||||
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
|
||||
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false">
|
||||
<file url="PROJECT" charset="UTF-8" />
|
||||
</component>
|
||||
<component name="EntryPointsManager">
|
||||
<entry_points version="2.0" />
|
||||
</component>
|
||||
<component name="GradleLocalSettings">
|
||||
<option name="availableTasks">
|
||||
<set>
|
||||
<GradleTaskDescriptor>
|
||||
<option name="description" value="Assembles the outputs of this project." />
|
||||
<option name="name" value="assemble" />
|
||||
</GradleTaskDescriptor>
|
||||
<GradleTaskDescriptor>
|
||||
<option name="description" value="Assembles and tests this project." />
|
||||
<option name="name" value="build" />
|
||||
</GradleTaskDescriptor>
|
||||
<GradleTaskDescriptor>
|
||||
<option name="description" value="Assembles and tests this project and all projects that depend on it." />
|
||||
<option name="name" value="buildDependents" />
|
||||
</GradleTaskDescriptor>
|
||||
<GradleTaskDescriptor>
|
||||
<option name="description" value="Assembles and tests this project and all projects it depends on." />
|
||||
<option name="name" value="buildNeeded" />
|
||||
</GradleTaskDescriptor>
|
||||
<GradleTaskDescriptor>
|
||||
<option name="description" value="Runs all checks." />
|
||||
<option name="name" value="check" />
|
||||
</GradleTaskDescriptor>
|
||||
<GradleTaskDescriptor>
|
||||
<option name="description" value="Assembles the main classes." />
|
||||
<option name="name" value="classes" />
|
||||
</GradleTaskDescriptor>
|
||||
<GradleTaskDescriptor>
|
||||
<option name="description" value="Deletes the build directory." />
|
||||
<option name="name" value="clean" />
|
||||
</GradleTaskDescriptor>
|
||||
<GradleTaskDescriptor>
|
||||
<option name="description" value="Compiles the main Java source." />
|
||||
<option name="name" value="compileJava" />
|
||||
</GradleTaskDescriptor>
|
||||
<GradleTaskDescriptor>
|
||||
<option name="description" value="Compiles the test Java source." />
|
||||
<option name="name" value="compileTestJava" />
|
||||
</GradleTaskDescriptor>
|
||||
<GradleTaskDescriptor>
|
||||
<option name="description" value="Assembles a jar archive containing the main classes." />
|
||||
<option name="name" value="jar" />
|
||||
</GradleTaskDescriptor>
|
||||
<GradleTaskDescriptor>
|
||||
<option name="description" value="Generates Javadoc API documentation for the main source code." />
|
||||
<option name="name" value="javadoc" />
|
||||
</GradleTaskDescriptor>
|
||||
<GradleTaskDescriptor>
|
||||
<option name="description" value="Processes the main resources." />
|
||||
<option name="name" value="processResources" />
|
||||
</GradleTaskDescriptor>
|
||||
<GradleTaskDescriptor>
|
||||
<option name="description" value="Processes the test resources." />
|
||||
<option name="name" value="processTestResources" />
|
||||
</GradleTaskDescriptor>
|
||||
<GradleTaskDescriptor>
|
||||
<option name="description" value="Runs the unit tests." />
|
||||
<option name="name" value="test" />
|
||||
</GradleTaskDescriptor>
|
||||
<GradleTaskDescriptor>
|
||||
<option name="description" value="Assembles the test classes." />
|
||||
<option name="name" value="testClasses" />
|
||||
</GradleTaskDescriptor>
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
<component name="GradleSettings">
|
||||
<option name="gradleHome" value="$PROJECT_DIR$/../../../gradle-1.8" />
|
||||
<option name="linkedProjectPath" value="" />
|
||||
</component>
|
||||
<component name="GradleUISettings2">
|
||||
<setting name="root" />
|
||||
</component>
|
||||
<component name="IdProvider" IDEtalkID="E31FCDAA3C23FC998F967D8CCAB8763A" />
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profiles>
|
||||
<profile version="1.0" is_locked="false">
|
||||
<option name="myName" value="Project Default" />
|
||||
<option name="myLocal" value="false" />
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||
<option name="processCode" value="true" />
|
||||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</profiles>
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<option name="myLocal" value="true" />
|
||||
<inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="loggerClassName" value="org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.apache.commons.logging.LogFactory,java.util.logging.Logger" />
|
||||
<option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||
<option name="processCode" value="true" />
|
||||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
<option name="PROJECT_PROFILE" value="Project Default" />
|
||||
<option name="USE_PROJECT_PROFILE" value="true" />
|
||||
<version value="1.0" />
|
||||
@@ -271,7 +203,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 6" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="false" assert-keyword="true" jdk-15="true" project-jdk-name="Java 7" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
<component name="SvnConfiguration" maxAnnotateRevisions="500" myUseAcceleration="nothing" myAutoUpdateAfterCommit="false" cleanupOnStartRun="false">
|
||||
@@ -295,5 +227,4 @@
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
|
||||
</project>
|
||||
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>net.sf.j8583</groupId>
|
||||
<artifactId>j8583</artifactId>
|
||||
<version>1.9.1</version>
|
||||
<version>1.10.2</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>
|
||||
@@ -27,12 +27,12 @@
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.6.1</version>
|
||||
<version>1.7.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.8.2</version>
|
||||
<version>4.11</version>
|
||||
<type>jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -28,8 +28,8 @@ package com.solab.iso8583;
|
||||
*/
|
||||
public interface CustomBinaryField<T> extends CustomField<T> {
|
||||
|
||||
public T decodeBinaryField(byte[] value, int offset, int length);
|
||||
T decodeBinaryField(byte[] value, int offset, int length);
|
||||
|
||||
public byte[] encodeBinaryField(T value);
|
||||
byte[] encodeBinaryField(T value);
|
||||
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ package com.solab.iso8583;
|
||||
*/
|
||||
public interface CustomField<T> {
|
||||
|
||||
public T decodeField(String value);
|
||||
T decodeField(String value);
|
||||
|
||||
public String encodeField(T value);
|
||||
String encodeField(T value);
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
package com.solab.iso8583;
|
||||
|
||||
import com.solab.iso8583.util.HexCodec;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
@@ -47,6 +49,7 @@ public class IsoMessage {
|
||||
private IsoValue[] fields = new IsoValue[129];
|
||||
/** Stores the optional ISO header. */
|
||||
private String isoHeader;
|
||||
private byte[] binIsoHeader;
|
||||
private int etx = -1;
|
||||
/** Flag to enforce secondary bitmap even if empty. */
|
||||
private boolean forceb2;
|
||||
@@ -62,9 +65,14 @@ public class IsoMessage {
|
||||
protected IsoMessage(String header) {
|
||||
isoHeader = header;
|
||||
}
|
||||
/** Creates a new message with the specified binary ISO header. This will be prepended to the message. */
|
||||
protected IsoMessage(byte[] binaryHeader) {
|
||||
binIsoHeader = binaryHeader;
|
||||
}
|
||||
|
||||
/** Tells the message to encode its bitmap in binary format, even if the message
|
||||
* itself is encoded as text. */
|
||||
* itself is encoded as text. This has no effect if the binary flag is set, which means
|
||||
* binary messages will always encode their bitmap in binary format. */
|
||||
public void setBinaryBitmap(boolean flag) {
|
||||
binBitmap = flag;
|
||||
}
|
||||
@@ -108,13 +116,25 @@ public class IsoMessage {
|
||||
/** 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;
|
||||
isoHeader = value;
|
||||
binIsoHeader = null;
|
||||
}
|
||||
/** Returns the ISO header that this message was created with. */
|
||||
public String getIsoHeader() {
|
||||
return isoHeader;
|
||||
}
|
||||
|
||||
/** 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 setBinaryIsoHeader(byte[] binaryHeader) {
|
||||
isoHeader = null;
|
||||
binIsoHeader = binaryHeader;
|
||||
}
|
||||
/** Returns the binary ISO header that this message was created with. */
|
||||
public byte[] getBinaryIsoHeader() {
|
||||
return binIsoHeader;
|
||||
}
|
||||
|
||||
/** Sets the ISO message type. Common values are 0x200, 0x210, 0x400, 0x410, 0x800, 0x810. */
|
||||
public void setType(int value) {
|
||||
type = value;
|
||||
@@ -124,7 +144,9 @@ public class IsoMessage {
|
||||
return type;
|
||||
}
|
||||
|
||||
/** Indicates whether the message should be binary. Default is false. */
|
||||
/** Indicates whether the message should be binary. Default is false.
|
||||
* To encode the message as text but the bitmap in binary format, you can set the
|
||||
* binaryBitmap flag. */
|
||||
public void setBinary(boolean flag) {
|
||||
binary = flag;
|
||||
}
|
||||
@@ -204,9 +226,9 @@ public class IsoMessage {
|
||||
} else {
|
||||
IsoValue<T> v = null;
|
||||
if (t.needsLength()) {
|
||||
v = new IsoValue<T>(t, value, length, encoder);
|
||||
v = new IsoValue<>(t, value, length, encoder);
|
||||
} else {
|
||||
v = new IsoValue<T>(t, value, encoder);
|
||||
v = new IsoValue<>(t, value, encoder);
|
||||
}
|
||||
v.setCharacterEncoding(encoding);
|
||||
fields[index] = v;
|
||||
@@ -338,8 +360,7 @@ public class IsoMessage {
|
||||
return bs;
|
||||
}
|
||||
|
||||
/** This calls writeInternal(), allowing applications to get the byte buffer containing the
|
||||
* message data, without the length header. */
|
||||
/** Writes the message to a memory stream and returns a byte array with the result. */
|
||||
public byte[] writeData() {
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
if (isoHeader != null) {
|
||||
@@ -348,7 +369,13 @@ public class IsoMessage {
|
||||
} catch (IOException ex) {
|
||||
//should never happen, writing to a ByteArrayOutputStream
|
||||
}
|
||||
}
|
||||
} else if (binIsoHeader != null) {
|
||||
try {
|
||||
bout.write(binIsoHeader);
|
||||
} catch (IOException ex) {
|
||||
//should never happen, writing to a ByteArrayOutputStream
|
||||
}
|
||||
}
|
||||
//Message Type
|
||||
if (binary) {
|
||||
bout.write((type & 0xff00) >> 8);
|
||||
@@ -429,6 +456,8 @@ public class IsoMessage {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (isoHeader != null) {
|
||||
sb.append(isoHeader);
|
||||
} else if (binIsoHeader != null) {
|
||||
sb.append("[0x").append(HexCodec.hexEncode(binIsoHeader, 0, binIsoHeader.length)).append("]");
|
||||
}
|
||||
sb.append(String.format("%04x", type));
|
||||
|
||||
@@ -458,6 +487,8 @@ public class IsoMessage {
|
||||
sb.append(String.format("%02d", desc.length()));
|
||||
} else if (v.getType() == IsoType.LLLBIN || v.getType() == IsoType.LLLVAR) {
|
||||
sb.append(String.format("%03d", desc.length()));
|
||||
} else if (v.getType() == IsoType.LLLLBIN || v.getType() == IsoType.LLLLVAR) {
|
||||
sb.append(String.format("%04d", desc.length()));
|
||||
}
|
||||
sb.append(desc);
|
||||
}
|
||||
@@ -502,4 +533,25 @@ public class IsoMessage {
|
||||
setField(i, null);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true is the message contains all the specified fields.
|
||||
* A convenience for m.hasField(x) && m.hasField(y) && m.hasField(z) && ... */
|
||||
public boolean hasEveryField(int... idx) {
|
||||
for (int i : idx) {
|
||||
if (!hasField(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/** Returns true is the message contains at least one of the specified fields.
|
||||
* A convenience for m.hasField(x) || m.hasField(y) || m.hasField(z) || ... */
|
||||
public boolean hasAnyField(int... idx) {
|
||||
for (int i : idx) {
|
||||
if (hasField(i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,13 @@ public enum IsoType {
|
||||
/** Similar to LLVAR but holds byte arrays instead of strings. */
|
||||
LLBIN(false, 0),
|
||||
/** Similar to LLLVAR but holds byte arrays instead of strings. */
|
||||
LLLBIN(false, 0);
|
||||
LLLBIN(false, 0),
|
||||
/** variable length with 4-digit header length. */
|
||||
LLLLVAR(false, 0),
|
||||
/** variable length byte array with 4-digit header length. */
|
||||
LLLLBIN(false, 0),
|
||||
/** Date in format yyMMddHHmmss. */
|
||||
DATE12(false,12);
|
||||
|
||||
private boolean needsLen;
|
||||
private int length;
|
||||
@@ -76,7 +82,7 @@ public enum IsoType {
|
||||
return length;
|
||||
}
|
||||
|
||||
/** Formats a Date if the receiver is DATE10, DATE4, DATE_EXP or TIME; throws an exception
|
||||
/** Formats a Date if the receiver is DATE10, DATE4, DATE_EXP, DATE12 or TIME; throws an exception
|
||||
* otherwise. */
|
||||
public String format(final Date value, final TimeZone tz) {
|
||||
final SimpleDateFormat sdf;
|
||||
@@ -88,6 +94,8 @@ public enum IsoType {
|
||||
sdf = new SimpleDateFormat("yyMM");
|
||||
} else if (this == TIME) {
|
||||
sdf = new SimpleDateFormat("HHmmss");
|
||||
} else if (this == DATE12) {
|
||||
sdf = new SimpleDateFormat("yyMMddHHmmss");
|
||||
} else {
|
||||
throw new IllegalArgumentException("Cannot format date as " + this);
|
||||
}
|
||||
@@ -110,7 +118,7 @@ public enum IsoType {
|
||||
} else {
|
||||
return String.format(String.format("%%-%ds", length), value);
|
||||
}
|
||||
} else if (this == LLVAR || this == LLLVAR) {
|
||||
} else if (this == LLVAR || this == LLLVAR || this == LLLLVAR) {
|
||||
return value;
|
||||
} else if (this == NUMERIC) {
|
||||
char[] c = new char[length];
|
||||
@@ -148,7 +156,7 @@ public enum IsoType {
|
||||
}
|
||||
return new String(c);
|
||||
|
||||
} else if (this == LLBIN || this == LLLBIN) {
|
||||
} else if (this == LLBIN || this == LLLBIN || this == LLLLBIN) {
|
||||
return value;
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot format String as " + this);
|
||||
@@ -162,11 +170,11 @@ public enum IsoType {
|
||||
throw new IllegalArgumentException("Numeric value is larger than intended length: " + value + " LEN " + length);
|
||||
}
|
||||
return x;
|
||||
} else if (this == ALPHA || this == LLVAR || this == LLLVAR) {
|
||||
} else if (this == ALPHA || this == LLVAR || this == LLLVAR || this == LLLLVAR) {
|
||||
return format(Long.toString(value), length);
|
||||
} else if (this == AMOUNT) {
|
||||
return String.format("%010d00", value);
|
||||
} else if (this == BINARY || this == LLBIN || this == LLLBIN) {
|
||||
} else if (this == BINARY || this == LLBIN || this == LLLBIN || this == LLLLBIN) {
|
||||
//TODO
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot format number as " + this);
|
||||
@@ -178,35 +186,35 @@ public enum IsoType {
|
||||
return String.format("%012d", value.movePointRight(2).longValue());
|
||||
} else if (this == NUMERIC) {
|
||||
return format(value.longValue(), length);
|
||||
} else if (this == ALPHA || this == LLVAR || this == LLLVAR) {
|
||||
} else if (this == ALPHA || this == LLVAR || this == LLLVAR || this == LLLLVAR) {
|
||||
return format(value.toString(), length);
|
||||
} else if (this == BINARY || this == LLBIN || this == LLLBIN) {
|
||||
} else if (this == BINARY || this == LLBIN || this == LLLBIN || this == LLLLBIN) {
|
||||
//TODO
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot format BigDecimal as " + this);
|
||||
}
|
||||
|
||||
public <T> IsoValue<T> value(T val, int len) {
|
||||
return new IsoValue<T>(this, val, len);
|
||||
return new IsoValue<>(this, val, len);
|
||||
}
|
||||
|
||||
public <T> IsoValue<T> value(T val) {
|
||||
return new IsoValue<T>(this, val);
|
||||
return new IsoValue<>(this, val);
|
||||
}
|
||||
|
||||
public <T> IsoValue<T> call(T val, int len) {
|
||||
return new IsoValue<T>(this, val, len);
|
||||
return new IsoValue<>(this, val, len);
|
||||
}
|
||||
|
||||
public <T> IsoValue<T> call(T val) {
|
||||
return new IsoValue<T>(this, val);
|
||||
return new IsoValue<>(this, val);
|
||||
}
|
||||
|
||||
public <T> IsoValue<T> apply(T val, int len) {
|
||||
return new IsoValue<T>(this, val, len);
|
||||
return new IsoValue<>(this, val, len);
|
||||
}
|
||||
public <T> IsoValue<T> apply(T val) {
|
||||
return new IsoValue<T>(this, val);
|
||||
return new IsoValue<>(this, val);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ package com.solab.iso8583;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
@@ -63,7 +64,7 @@ public class IsoValue<T> implements Cloneable {
|
||||
encoder = custom;
|
||||
type = t;
|
||||
this.value = value;
|
||||
if (type == IsoType.LLVAR || type == IsoType.LLLVAR) {
|
||||
if (type == IsoType.LLVAR || type == IsoType.LLLVAR || type == IsoType.LLLLVAR) {
|
||||
if (custom == null) {
|
||||
length = value.toString().length();
|
||||
} else {
|
||||
@@ -77,8 +78,10 @@ public class IsoValue<T> implements Cloneable {
|
||||
throw new IllegalArgumentException("LLVAR can only hold values up to 99 chars");
|
||||
} else if (t == IsoType.LLLVAR && length > 999) {
|
||||
throw new IllegalArgumentException("LLLVAR can only hold values up to 999 chars");
|
||||
}
|
||||
} else if (type == IsoType.LLBIN || type == IsoType.LLLBIN) {
|
||||
} else if (t == IsoType.LLLLVAR && length > 9999) {
|
||||
throw new IllegalArgumentException("LLLLVAR can only hold values up to 9999 chars");
|
||||
}
|
||||
} else if (type == IsoType.LLBIN || type == IsoType.LLLBIN || type == IsoType.LLLLBIN) {
|
||||
if (custom == null) {
|
||||
if (value instanceof byte[]) {
|
||||
length = ((byte[])value).length;
|
||||
@@ -98,6 +101,8 @@ public class IsoValue<T> implements Cloneable {
|
||||
throw new IllegalArgumentException("LLBIN can only hold values up to 99 chars");
|
||||
} else if (t == IsoType.LLLBIN && length > 999) {
|
||||
throw new IllegalArgumentException("LLLBIN can only hold values up to 999 chars");
|
||||
} else if (t == IsoType.LLLLBIN && length > 9999) {
|
||||
throw new IllegalArgumentException("LLLLBIN can only hold values up to 9999 chars");
|
||||
}
|
||||
} else {
|
||||
length = type.getLength();
|
||||
@@ -122,7 +127,7 @@ public class IsoValue<T> implements Cloneable {
|
||||
encoder = custom;
|
||||
if (length == 0 && t.needsLength()) {
|
||||
throw new IllegalArgumentException(String.format("Length must be greater than zero for type %s (value '%s')", t, val));
|
||||
} else if (t == IsoType.LLVAR || t == IsoType.LLLVAR) {
|
||||
} else if (t == IsoType.LLVAR || t == IsoType.LLLVAR || t == IsoType.LLLLVAR) {
|
||||
if (len == 0) {
|
||||
length = custom == null ? val.toString().length() : custom.encodeField(value).length();
|
||||
}
|
||||
@@ -130,8 +135,10 @@ public class IsoValue<T> implements Cloneable {
|
||||
throw new IllegalArgumentException("LLVAR can only hold values up to 99 chars");
|
||||
} else if (t == IsoType.LLLVAR && length > 999) {
|
||||
throw new IllegalArgumentException("LLLVAR can only hold values up to 999 chars");
|
||||
} else if (t == IsoType.LLLLVAR && length > 9999) {
|
||||
throw new IllegalArgumentException("LLLLVAR can only hold values up to 9999 chars");
|
||||
}
|
||||
} else if (t == IsoType.LLBIN || t == IsoType.LLLBIN) {
|
||||
} else if (t == IsoType.LLBIN || t == IsoType.LLLBIN || t == IsoType.LLLLBIN) {
|
||||
if (len == 0) {
|
||||
if (custom == null) {
|
||||
length = ((byte[])val).length;
|
||||
@@ -146,6 +153,8 @@ public class IsoValue<T> implements Cloneable {
|
||||
throw new IllegalArgumentException("LLBIN can only hold values up to 99 chars");
|
||||
} else if (t == IsoType.LLLBIN && length > 999) {
|
||||
throw new IllegalArgumentException("LLLBIN can only hold values up to 999 chars");
|
||||
} else if (t == IsoType.LLLLBIN && length > 9999) {
|
||||
throw new IllegalArgumentException("LLLLBIN can only hold values up to 9999 chars");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,10 +200,12 @@ public class IsoValue<T> implements Cloneable {
|
||||
if (type == IsoType.NUMERIC || type == IsoType.AMOUNT) {
|
||||
if (type == IsoType.AMOUNT) {
|
||||
if (value instanceof BigDecimal) {
|
||||
return type.format((BigDecimal)value, 12);
|
||||
return type.format((BigDecimal) value, 12);
|
||||
} else {
|
||||
return type.format(value.toString(), 12);
|
||||
}
|
||||
} else if (value instanceof BigInteger) {
|
||||
return type.format(encoder == null ? value.toString() : encoder.encodeField(value), length);
|
||||
} else if (value instanceof Number) {
|
||||
return type.format(((Number)value).longValue(), length);
|
||||
} else {
|
||||
@@ -202,7 +213,7 @@ public class IsoValue<T> implements Cloneable {
|
||||
}
|
||||
} else if (type == IsoType.ALPHA) {
|
||||
return type.format(encoder == null ? value.toString() : encoder.encodeField(value), length);
|
||||
} else if (type == IsoType.LLVAR || type == IsoType.LLLVAR) {
|
||||
} else if (type == IsoType.LLVAR || type == IsoType.LLLVAR || type == IsoType.LLLLVAR) {
|
||||
return encoder == null ? value.toString() : encoder.encodeField(value);
|
||||
} else if (value instanceof Date) {
|
||||
return type.format((Date)value, tz);
|
||||
@@ -213,7 +224,7 @@ public class IsoValue<T> implements Cloneable {
|
||||
} else {
|
||||
return type.format(encoder == null ? value.toString() : encoder.encodeField(value), length * 2);
|
||||
}
|
||||
} else if (type == IsoType.LLBIN || type == IsoType.LLLBIN) {
|
||||
} else if (type == IsoType.LLBIN || type == IsoType.LLLBIN || type == IsoType.LLLLBIN) {
|
||||
if (value instanceof byte[]) {
|
||||
final byte[] _v = (byte[])value;
|
||||
return encoder == null ? HexCodec.hexEncode(_v, 0, _v.length) : encoder.encodeField(value);
|
||||
@@ -242,7 +253,8 @@ public class IsoValue<T> implements Cloneable {
|
||||
return false;
|
||||
}
|
||||
IsoValue<?> comp = (IsoValue<?>)other;
|
||||
return (comp.getType() == getType() && comp.getValue().equals(getValue()) && comp.getLength() == getLength());
|
||||
return (comp.getType() == getType() && comp.getValue().equals(getValue())
|
||||
&& comp.getLength() == getLength());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -255,11 +267,21 @@ public class IsoValue<T> implements Cloneable {
|
||||
return encoder;
|
||||
}
|
||||
|
||||
protected void writeLengthHeader(final int l, final OutputStream outs, final int digits,
|
||||
protected void writeLengthHeader(final int l, final OutputStream outs, final IsoType type,
|
||||
final boolean binary, final boolean forceStringEncoding)
|
||||
throws IOException {
|
||||
final int digits;
|
||||
if (type == IsoType.LLLLBIN || type == IsoType.LLLLVAR) {
|
||||
digits = 4;
|
||||
} else if (type == IsoType.LLLBIN || type == IsoType.LLLVAR) {
|
||||
digits = 3;
|
||||
} else {
|
||||
digits = 2;
|
||||
}
|
||||
if (binary) {
|
||||
if (digits == 3) {
|
||||
if (digits == 4) {
|
||||
outs.write((((l % 10000) / 1000) << 4) | ((l % 1000)/100));
|
||||
} else if (digits == 3) {
|
||||
outs.write(l / 100); //00 to 09 automatically in BCD
|
||||
}
|
||||
//BCD encode the rest of the length
|
||||
@@ -271,11 +293,16 @@ public class IsoValue<T> implements Cloneable {
|
||||
lhead = '0' + lhead;
|
||||
} else if (ldiff == 2) {
|
||||
lhead = "00" + lhead;
|
||||
} else if (ldiff == 3) {
|
||||
lhead = "000" + lhead;
|
||||
}
|
||||
outs.write(encoding == null ? lhead.getBytes():lhead.getBytes(encoding));
|
||||
} else {
|
||||
//write the length in ASCII
|
||||
if (digits == 3) {
|
||||
if (digits == 4) {
|
||||
outs.write((l/1000)+48);
|
||||
outs.write(((l%1000)/100)+48);
|
||||
} else if (digits == 3) {
|
||||
outs.write((l / 100) + 48);
|
||||
}
|
||||
if (l >= 10) {
|
||||
@@ -295,10 +322,10 @@ public class IsoValue<T> implements Cloneable {
|
||||
* 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) {
|
||||
writeLengthHeader(length, outs, type == IsoType.LLLVAR ? 3 : 2, binary, forceStringEncoding);
|
||||
} else if (type == IsoType.LLBIN || type == IsoType.LLLBIN) {
|
||||
writeLengthHeader(binary ? length : length*2, outs, type == IsoType.LLLBIN ? 3 : 2, binary, forceStringEncoding);
|
||||
if (type == IsoType.LLLVAR || type == IsoType.LLVAR || type == IsoType.LLLLVAR) {
|
||||
writeLengthHeader(length, outs, type, binary, forceStringEncoding);
|
||||
} else if (type == IsoType.LLBIN || type == IsoType.LLLBIN || type == IsoType.LLLLBIN) {
|
||||
writeLengthHeader(binary ? length : length*2, outs, type, binary, forceStringEncoding);
|
||||
} else if (binary) {
|
||||
//numeric types in binary are coded like this
|
||||
byte[] buf = null;
|
||||
@@ -306,7 +333,8 @@ public class IsoValue<T> implements Cloneable {
|
||||
buf = new byte[(length / 2) + (length % 2)];
|
||||
} else if (type == IsoType.AMOUNT) {
|
||||
buf = new byte[6];
|
||||
} else if (type == IsoType.DATE10 || type == IsoType.DATE4 || type == IsoType.DATE_EXP || type == IsoType.TIME) {
|
||||
} else if (type == IsoType.DATE10 || type == IsoType.DATE4 ||
|
||||
type == IsoType.DATE_EXP || type == IsoType.TIME || type == IsoType.DATE12) {
|
||||
buf = new byte[length / 2];
|
||||
}
|
||||
//Encode in BCD if it's one of these types
|
||||
@@ -316,7 +344,7 @@ public class IsoValue<T> implements Cloneable {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (binary && (type == IsoType.BINARY || type == IsoType.LLBIN || type == IsoType.LLLBIN)) {
|
||||
if (binary && (type == IsoType.BINARY || type == IsoType.LLBIN || type == IsoType.LLLBIN || type == IsoType.LLLLBIN)) {
|
||||
int missing = 0;
|
||||
if (value instanceof byte[]) {
|
||||
outs.write((byte[])value);
|
||||
|
||||
@@ -49,24 +49,26 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
/** This map stores the message template for each message type. */
|
||||
private Map<Integer, T> typeTemplates = new HashMap<Integer, T>();
|
||||
private Map<Integer, T> typeTemplates = new HashMap<>();
|
||||
/** Stores the information needed to parse messages sorted by type. */
|
||||
protected Map<Integer, Map<Integer, FieldParseInfo>> parseMap = new HashMap<Integer, Map<Integer, FieldParseInfo>>();
|
||||
protected Map<Integer, Map<Integer, FieldParseInfo>> parseMap = new HashMap<>();
|
||||
/** Stores the field numbers to be parsed, in order of appearance. */
|
||||
protected Map<Integer, List<Integer>> parseOrder = new HashMap<Integer, List<Integer>>();
|
||||
protected Map<Integer, List<Integer>> parseOrder = new HashMap<>();
|
||||
|
||||
private TraceNumberGenerator traceGen;
|
||||
/** The ISO header to be included in each message type. */
|
||||
private Map<Integer, String> isoHeaders = new HashMap<Integer, String>();
|
||||
private Map<Integer, String> isoHeaders = new HashMap<>();
|
||||
private Map<Integer, byte[]> binIsoHeaders = new HashMap<>();
|
||||
/** A map for the custom field encoder/decoders, keyed by field number. */
|
||||
@SuppressWarnings("rawtypes")
|
||||
private Map<Integer, CustomField> customFields = new HashMap<Integer, CustomField>();
|
||||
private Map<Integer, CustomField> customFields = new HashMap<>();
|
||||
/** Indicates if the current date should be set on new messages (field 7). */
|
||||
private boolean setDate;
|
||||
/** Indicates if the factory should create binary messages and also parse binary messages. */
|
||||
private boolean useBinary;
|
||||
private int etx = -1;
|
||||
/** Flag to specify if missing fields should be ignored as long as they're at the end of the message. */
|
||||
/** Flag to specify if missing fields should be ignored as long as they're at
|
||||
* the end of the message. */
|
||||
private boolean ignoreLast;
|
||||
private boolean forceb2;
|
||||
private boolean binBitmap;
|
||||
@@ -124,13 +126,14 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the encoding used to parse ALPHA, LLVAR and LLLVAR fields. The default is the file.encoding
|
||||
* system property. */
|
||||
/** Returns the encoding used to parse ALPHA, LLVAR and LLLVAR fields. The default is the
|
||||
* file.encoding system property. */
|
||||
public String getCharacterEncoding() {
|
||||
return encoding;
|
||||
}
|
||||
|
||||
/** Sets or clears the flag to pass to new messages, to include a secondary bitmap even if it's not needed. */
|
||||
/** Sets or clears the flag to pass to new messages, to include a secondary bitmap
|
||||
* even if it's not needed. */
|
||||
public void setForceSecondaryBitmap(boolean flag) {
|
||||
forceb2 = flag;
|
||||
}
|
||||
@@ -209,7 +212,12 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
* messages, then the returned message will be written using binary coding.
|
||||
* @param type The message type, for example 0x200, 0x400, etc. */
|
||||
public T newMessage(int type) {
|
||||
T m = createIsoMessage(isoHeaders.get(type));
|
||||
T m;
|
||||
if (binIsoHeaders.get(type) != null) {
|
||||
m = createIsoMessageWithBinaryHeader(binIsoHeaders.get(type));
|
||||
} else {
|
||||
m = createIsoMessage(isoHeaders.get(type));
|
||||
}
|
||||
m.setType(type);
|
||||
m.setEtx(etx);
|
||||
m.setBinary(useBinary);
|
||||
@@ -270,7 +278,7 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
return resp;
|
||||
}
|
||||
|
||||
/** Sets the timezone for the specified FieldParseInfo, if it's indeed for parsing dates. */
|
||||
/** Sets the timezone for the specified FieldParseInfo, if it's needed for parsing dates. */
|
||||
public void setTimezoneForParseGuide(int messageType, int field, TimeZone tz) {
|
||||
Map<Integer, FieldParseInfo> guide = parseMap.get(messageType);
|
||||
if (guide != null) {
|
||||
@@ -284,19 +292,33 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
field, messageType);
|
||||
}
|
||||
|
||||
/** Convenience for parseMessage(buf, isoHeaderLength, false) */
|
||||
public T parseMessage(byte[] buf, int isoHeaderLength)
|
||||
throws ParseException, UnsupportedEncodingException {
|
||||
return parseMessage(buf, isoHeaderLength, false);
|
||||
}
|
||||
|
||||
/** 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.
|
||||
* @param buf The byte buffer containing the message. Must not include the length header.
|
||||
* @param isoHeaderLength The expected length of the ISO header, after which the message type
|
||||
* and the rest of the message must come. */
|
||||
public T parseMessage(byte[] buf, int isoHeaderLength)
|
||||
throws ParseException, UnsupportedEncodingException {
|
||||
public T parseMessage(byte[] buf, int isoHeaderLength, boolean binaryIsoHeader)
|
||||
throws ParseException, UnsupportedEncodingException {
|
||||
final int minlength = isoHeaderLength+(useBinary?2:4)+(binBitmap||useBinary ? 8:16);
|
||||
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, encoding) : null);
|
||||
final T m;
|
||||
if (binaryIsoHeader && isoHeaderLength > 0) {
|
||||
byte[] _bih = new byte[isoHeaderLength];
|
||||
System.arraycopy(buf, 0, _bih, 0, isoHeaderLength);
|
||||
m = createIsoMessageWithBinaryHeader(_bih);
|
||||
} else {
|
||||
m = createIsoMessage(isoHeaderLength > 0 ?
|
||||
new String(buf, 0, isoHeaderLength, encoding) : null);
|
||||
}
|
||||
m.setCharacterEncoding(encoding);
|
||||
final int type;
|
||||
if (useBinary) {
|
||||
@@ -359,7 +381,7 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
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);
|
||||
bs.set(pos++, ((bitmapBuffer[i] - 55) & 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);
|
||||
@@ -386,7 +408,7 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
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);
|
||||
bs.set(pos++, ((bitmapBuffer[i] - 55) & 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);
|
||||
@@ -410,7 +432,10 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
if (index == null) {
|
||||
log.error(String.format("ISO8583 MessageFactory has no parsing guide for message type %04x [%s]",
|
||||
type, new String(buf)));
|
||||
return null;
|
||||
throw new ParseException(String.format(
|
||||
"ISO8583 MessageFactory has no parsing guide for message type %04x [%s]",
|
||||
type,
|
||||
new String(buf)), 0);
|
||||
}
|
||||
//First we check if the message contains fields not specified in the parsing template
|
||||
boolean abandon = false;
|
||||
@@ -421,7 +446,7 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
}
|
||||
}
|
||||
if (abandon) {
|
||||
return m;
|
||||
throw new ParseException("ISO8583 MessageFactory cannot parse fields", 0);
|
||||
}
|
||||
//Now we parse each field
|
||||
if (useBinary) {
|
||||
@@ -440,17 +465,22 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
m.setField(i, val);
|
||||
if (val != null) {
|
||||
if (val.getType() == IsoType.NUMERIC || val.getType() == IsoType.DATE10
|
||||
|| val.getType() == IsoType.DATE4 || val.getType() == IsoType.DATE_EXP
|
||||
|| val.getType() == IsoType.AMOUNT || val.getType() == IsoType.TIME) {
|
||||
|| val.getType() == IsoType.DATE4 || val.getType() == IsoType.DATE12
|
||||
|| val.getType() == IsoType.DATE_EXP
|
||||
|| val.getType() == IsoType.AMOUNT
|
||||
|| val.getType() == IsoType.TIME) {
|
||||
pos += (val.getLength() / 2) + (val.getLength() % 2);
|
||||
} else {
|
||||
pos += val.getLength();
|
||||
}
|
||||
if (val.getType() == IsoType.LLVAR || val.getType() == IsoType.LLBIN) {
|
||||
pos++;
|
||||
} else if (val.getType() == IsoType.LLLVAR || val.getType() == IsoType.LLLBIN) {
|
||||
pos += 2;
|
||||
}
|
||||
} else if (val.getType() == IsoType.LLLVAR
|
||||
|| val.getType() == IsoType.LLLBIN
|
||||
|| val.getType() == IsoType.LLLLVAR
|
||||
|| val.getType() == IsoType.LLLLBIN) {
|
||||
pos += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -475,7 +505,9 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
pos += 2;
|
||||
} else if (val.getType() == IsoType.LLLVAR || val.getType() == IsoType.LLLBIN) {
|
||||
pos += 3;
|
||||
}
|
||||
} else if (val.getType() == IsoType.LLLLVAR || val.getType() == IsoType.LLLLBIN) {
|
||||
pos += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -495,7 +527,18 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
return (T)new IsoMessage(header);
|
||||
}
|
||||
|
||||
/** Sets whether the factory should set the current date on newly created messages,
|
||||
/** Creates a Iso message with the specified binary ISO header.
|
||||
* Override this method in the subclass to provide your
|
||||
* own implementations of IsoMessage.
|
||||
* @param binHeader The optional ISO header that goes before the message type
|
||||
* @return IsoMessage
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected T createIsoMessageWithBinaryHeader(byte[] binHeader) {
|
||||
return (T)new IsoMessage(binHeader);
|
||||
}
|
||||
|
||||
/** Sets whether the factory should set the current date on newly created messages,
|
||||
* in field 7. Default is false. */
|
||||
public void setAssignDate(boolean flag) {
|
||||
setDate = flag;
|
||||
@@ -532,6 +575,7 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
isoHeaders.remove(type);
|
||||
} else {
|
||||
isoHeaders.put(type, value);
|
||||
binIsoHeaders.remove(type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,6 +584,22 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
return isoHeaders.get(type);
|
||||
}
|
||||
|
||||
/** Sets the ISO header for a specific message type, in binary format.
|
||||
* @param type The message type, for example 0x200.
|
||||
* @param value The ISO header, or NULL to remove any headers for this message type. */
|
||||
public void setBinaryIsoHeader(int type, byte[] value) {
|
||||
if (value == null) {
|
||||
binIsoHeaders.remove(type);
|
||||
} else {
|
||||
binIsoHeaders.put(type, value);
|
||||
isoHeaders.remove(type);
|
||||
}
|
||||
}
|
||||
/** Returns the binary ISO header used for the specified type. */
|
||||
public byte[] getBinaryIsoHeader(int type) {
|
||||
return binIsoHeaders.get(type);
|
||||
}
|
||||
|
||||
/** Adds a message template to the factory. If there was a template for the same
|
||||
* message type as the new one, it is overwritten. */
|
||||
public void addMessageTemplate(T templ) {
|
||||
@@ -554,7 +614,7 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
}
|
||||
|
||||
/** Returns the template for the specified message type. This allows templates to be modified
|
||||
* programatically. */
|
||||
* programmatically. */
|
||||
public T getMessageTemplate(int type) {
|
||||
return typeTemplates.get(type);
|
||||
}
|
||||
@@ -566,6 +626,7 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
parseMap = Collections.unmodifiableMap(parseMap);
|
||||
parseOrder = Collections.unmodifiableMap(parseOrder);
|
||||
isoHeaders = Collections.unmodifiableMap(isoHeaders);
|
||||
binIsoHeaders = Collections.unmodifiableMap(binIsoHeaders);
|
||||
customFields = Collections.unmodifiableMap(customFields);
|
||||
}
|
||||
|
||||
@@ -576,10 +637,11 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
* of field to expect. The keys will be the field numbers. */
|
||||
public void setParseMap(int type, Map<Integer, FieldParseInfo> map) {
|
||||
parseMap.put(type, map);
|
||||
ArrayList<Integer> index = new ArrayList<Integer>();
|
||||
ArrayList<Integer> index = new ArrayList<>();
|
||||
index.addAll(map.keySet());
|
||||
Collections.sort(index);
|
||||
log.trace(String.format("ISO8583 MessageFactory adding parse map for type %04x with fields %s", type, index));
|
||||
log.trace(String.format("ISO8583 MessageFactory adding parse map for type %04x with fields %s",
|
||||
type, index));
|
||||
parseOrder.put(type, index);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,19 +41,19 @@ public class CompositeField implements CustomBinaryField<CompositeField> {
|
||||
@SuppressWarnings("rawtypes")
|
||||
public CompositeField addValue(IsoValue<?> v) {
|
||||
if (values == null) {
|
||||
values = new ArrayList<IsoValue>(4);
|
||||
values = new ArrayList<>(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));
|
||||
return addValue(t.needsLength() ? new IsoValue<>(t, val, length, encoder)
|
||||
: new IsoValue<>(t, val, encoder));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> IsoValue<T> getField(int idx) {
|
||||
if (idx < 0 || idx > values.size())return null;
|
||||
if (idx < 0 || idx >= values.size())return null;
|
||||
return values.get(idx);
|
||||
}
|
||||
public <T> T getObjectValue(int idx) {
|
||||
@@ -69,7 +69,7 @@ public class CompositeField implements CustomBinaryField<CompositeField> {
|
||||
}
|
||||
public CompositeField addParser(FieldParseInfo fpi) {
|
||||
if (parsers == null) {
|
||||
parsers = new ArrayList<FieldParseInfo>(4);
|
||||
parsers = new ArrayList<>(4);
|
||||
}
|
||||
parsers.add(fpi);
|
||||
return this;
|
||||
@@ -78,22 +78,24 @@ public class CompositeField implements CustomBinaryField<CompositeField> {
|
||||
@Override
|
||||
public CompositeField decodeBinaryField(byte[] buf, int offset, int length) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
List<IsoValue> vals = new ArrayList<IsoValue>(parsers.size());
|
||||
int pos = 0;
|
||||
List<IsoValue> vals = new ArrayList<>(parsers.size());
|
||||
int pos = offset;
|
||||
try {
|
||||
for (FieldParseInfo fpi : parsers) {
|
||||
IsoValue<?> v = fpi.parseBinary(0, buf, pos, null);
|
||||
IsoValue<?> v = fpi.parseBinary(0, buf, pos, fpi.getDecoder());
|
||||
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) {
|
||||
|| v.getType() == IsoType.AMOUNT || v.getType() == IsoType.TIME
|
||||
|| v.getType() == IsoType.DATE12) {
|
||||
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) {
|
||||
} else if (v.getType() == IsoType.LLLVAR || v.getType() == IsoType.LLLBIN
|
||||
|| v.getType() == IsoType.LLLLVAR || v.getType() == IsoType.LLLLBIN) {
|
||||
pos+=2;
|
||||
}
|
||||
vals.add(v);
|
||||
@@ -114,18 +116,20 @@ public class CompositeField implements CustomBinaryField<CompositeField> {
|
||||
@Override
|
||||
public CompositeField decodeField(String value) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
List<IsoValue> vals = new ArrayList<IsoValue>(parsers.size());
|
||||
List<IsoValue> vals = new ArrayList<>(parsers.size());
|
||||
byte[] buf = value.getBytes();
|
||||
int pos = 0;
|
||||
try {
|
||||
for (FieldParseInfo fpi : parsers) {
|
||||
IsoValue<?> v = fpi.parse(0, buf, pos, null);
|
||||
IsoValue<?> v = fpi.parse(0, buf, pos, fpi.getDecoder());
|
||||
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;
|
||||
} else if (v.getType() == IsoType.LLLLBIN || v.getType() == IsoType.LLLLVAR) {
|
||||
pos+=4;
|
||||
}
|
||||
vals.add(v);
|
||||
}
|
||||
@@ -133,10 +137,7 @@ public class CompositeField implements CustomBinaryField<CompositeField> {
|
||||
final CompositeField f = new CompositeField();
|
||||
f.setValues(vals);
|
||||
return f;
|
||||
} catch (ParseException ex) {
|
||||
log.error("Decoding CompositeField", ex);
|
||||
return null;
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
} catch (ParseException | UnsupportedEncodingException ex) {
|
||||
log.error("Decoding CompositeField", ex);
|
||||
return null;
|
||||
}
|
||||
@@ -158,23 +159,19 @@ public class CompositeField implements CustomBinaryField<CompositeField> {
|
||||
|
||||
@Override
|
||||
public String encodeField(CompositeField value) {
|
||||
final ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
byte[] buf = null;
|
||||
try {
|
||||
String encoding = null;
|
||||
final ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
for (IsoValue<?> v : value.getValues()) {
|
||||
v.write(bout, false, true);
|
||||
if (encoding == null)encoding = v.getCharacterEncoding();
|
||||
}
|
||||
buf = bout.toByteArray();
|
||||
final byte[] 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 "";
|
||||
}
|
||||
return new String(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,7 +27,7 @@ import com.solab.iso8583.TraceNumberGenerator;
|
||||
*/
|
||||
public class SimpleTraceGenerator implements TraceNumberGenerator {
|
||||
|
||||
private int value = 0;
|
||||
private volatile int value = 0;
|
||||
|
||||
/** Creates a new instance that will use the specified initial value. This means
|
||||
* the first nextTrace() call will return this number.
|
||||
|
||||
@@ -52,11 +52,11 @@ public abstract class AlphaNumericFieldParseInfo extends FieldParseInfo {
|
||||
_v = new String(buf, pos, buf.length-pos, getCharacterEncoding()).substring(0, length);
|
||||
}
|
||||
if (custom == null) {
|
||||
return new IsoValue<String>(type, _v, length, null);
|
||||
return new IsoValue<>(type, _v, length, null);
|
||||
} else {
|
||||
T decoded = custom.decodeField(_v);
|
||||
return decoded == null ? new IsoValue<String>(type, _v, length, null) :
|
||||
new IsoValue<T>(type, decoded, length, custom);
|
||||
return decoded == null ? new IsoValue<>(type, _v, length, null) :
|
||||
new IsoValue<>(type, decoded, length, custom);
|
||||
}
|
||||
} catch (StringIndexOutOfBoundsException ex) {
|
||||
throw new ParseException(String.format(
|
||||
|
||||
@@ -49,12 +49,12 @@ public class AlphaParseInfo extends AlphaNumericFieldParseInfo {
|
||||
}
|
||||
try {
|
||||
if (custom == null) {
|
||||
return new IsoValue<String>(type, new String(buf, pos, length, getCharacterEncoding()), length, null);
|
||||
return new IsoValue<>(type, new String(buf, pos, length, getCharacterEncoding()), length, null);
|
||||
} else {
|
||||
T decoded = custom.decodeField(new String(buf, pos, length, getCharacterEncoding()));
|
||||
return decoded == null ?
|
||||
new IsoValue<String>(type, new String(buf, pos, length, getCharacterEncoding()), length, null) :
|
||||
new IsoValue<T>(type, decoded, length, custom);
|
||||
new IsoValue<>(type, new String(buf, pos, length, getCharacterEncoding()), length, null) :
|
||||
new IsoValue<>(type, decoded, length, custom);
|
||||
}
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
throw new ParseException(String.format(
|
||||
|
||||
@@ -51,7 +51,7 @@ public class AmountParseInfo extends FieldParseInfo {
|
||||
}
|
||||
String c = new String(buf, pos, 12, getCharacterEncoding());
|
||||
try {
|
||||
return new IsoValue<BigDecimal>(type, new BigDecimal(c).movePointLeft(2));
|
||||
return new IsoValue<>(type, new BigDecimal(c).movePointLeft(2));
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new ParseException(String.format("Cannot read amount '%s' field %d pos %d",
|
||||
c, field, pos), pos);
|
||||
@@ -76,7 +76,7 @@ public class AmountParseInfo extends FieldParseInfo {
|
||||
}
|
||||
}
|
||||
try {
|
||||
return new IsoValue<BigDecimal>(IsoType.AMOUNT, new BigDecimal(new String(digits)));
|
||||
return new IsoValue<>(IsoType.AMOUNT, new BigDecimal(new String(digits)));
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new ParseException(String.format("Cannot read amount '%s' field %d pos %d",
|
||||
new String(digits), field, pos), pos);
|
||||
|
||||
@@ -52,11 +52,11 @@ public class BinaryParseInfo extends FieldParseInfo {
|
||||
}
|
||||
byte[] binval = HexCodec.hexDecode(new String(buf, pos, length*2));
|
||||
if (custom == null) {
|
||||
return new IsoValue<byte[]>(type, binval, binval.length, null);
|
||||
return new IsoValue<>(type, binval, binval.length, null);
|
||||
} else {
|
||||
T dec = custom.decodeField(new String(buf, pos, length*2, getCharacterEncoding()));
|
||||
return dec == null ? new IsoValue<byte[]>(type, binval, binval.length, null) :
|
||||
new IsoValue<T>(type, dec, length, custom);
|
||||
return dec == null ? new IsoValue<>(type, binval, binval.length, null) :
|
||||
new IsoValue<>(type, dec, length, custom);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,11 +75,11 @@ public class BinaryParseInfo extends FieldParseInfo {
|
||||
byte[] _v = new byte[length];
|
||||
System.arraycopy(buf, pos, _v, 0, length);
|
||||
if (custom == null) {
|
||||
return new IsoValue<byte[]>(type, _v, length, null);
|
||||
return new IsoValue<>(type, _v, length, null);
|
||||
} else {
|
||||
T dec = custom.decodeField(HexCodec.hexEncode(_v, 0, _v.length));
|
||||
return dec == null ? new IsoValue<byte[]>(type, _v, length, null) :
|
||||
new IsoValue<T>(type, dec, length, custom);
|
||||
return dec == null ? new IsoValue<>(type, _v, length, null) :
|
||||
new IsoValue<>(type, dec, length, custom);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,29 +20,30 @@ package com.solab.iso8583.parse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.net.URL;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import com.solab.iso8583.*;
|
||||
import com.solab.iso8583.codecs.CompositeField;
|
||||
import com.solab.iso8583.util.HexCodec;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.EntityResolver;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import com.solab.iso8583.CustomField;
|
||||
import com.solab.iso8583.IsoMessage;
|
||||
import com.solab.iso8583.IsoType;
|
||||
import com.solab.iso8583.MessageFactory;
|
||||
|
||||
/** This class is used to parse a XML configuration file and configure
|
||||
* a MessageFactory with the values from it.
|
||||
*
|
||||
@@ -58,7 +59,7 @@ public class ConfigParser {
|
||||
ClassLoader loader) throws IOException {
|
||||
if (loader.getResource("j8583.xml") == null) {
|
||||
log.warn("ISO8583 ConfigParser cannot find j8583.xml, returning empty message factory");
|
||||
return new MessageFactory<IsoMessage>();
|
||||
return new MessageFactory<>();
|
||||
} else {
|
||||
return createFromClasspathConfig(loader, "j8583.xml");
|
||||
}
|
||||
@@ -81,33 +82,34 @@ public class ConfigParser {
|
||||
* using MessageFactory's ClassLoader. */
|
||||
public static MessageFactory<IsoMessage> createFromClasspathConfig(
|
||||
ClassLoader loader, String path) throws IOException {
|
||||
InputStream ins = loader.getResourceAsStream(path);
|
||||
MessageFactory<IsoMessage> mfact = new MessageFactory<IsoMessage>();
|
||||
if (ins != null) {
|
||||
log.debug("ISO8583 Parsing config from classpath file {}", path);
|
||||
try {
|
||||
parse(mfact, ins);
|
||||
} finally {
|
||||
ins.close();
|
||||
}
|
||||
} else {
|
||||
log.warn("ISO8583 File not found in classpath: {}", path);
|
||||
}
|
||||
MessageFactory<IsoMessage> mfact = new MessageFactory<>();
|
||||
try (InputStream ins = loader.getResourceAsStream(path)) {
|
||||
if (ins != null) {
|
||||
log.debug("ISO8583 Parsing config from classpath file {}", path);
|
||||
parse(mfact, new InputSource(ins));
|
||||
} else {
|
||||
log.error("ISO8583 File not found in classpath: {}", path);
|
||||
}
|
||||
}
|
||||
return mfact;
|
||||
}
|
||||
|
||||
/** Creates a message factory from the file located at the specified URL. */
|
||||
public static MessageFactory<IsoMessage> createFromUrl(URL url) throws IOException {
|
||||
MessageFactory<IsoMessage> mfact = new MessageFactory<IsoMessage>();
|
||||
InputStream stream = url.openStream();
|
||||
try {
|
||||
parse(mfact, stream);
|
||||
} finally {
|
||||
stream.close();
|
||||
MessageFactory<IsoMessage> mfact = new MessageFactory<>();
|
||||
try (InputStream stream = url.openStream()) {
|
||||
parse(mfact, new InputSource(stream));
|
||||
}
|
||||
return mfact;
|
||||
}
|
||||
|
||||
/** Creates a messageFactory from the XML contained in the specified Reader. */
|
||||
public static MessageFactory<IsoMessage> createFromReader(Reader reader) throws IOException {
|
||||
MessageFactory<IsoMessage> mfact = new MessageFactory<>();
|
||||
parse(mfact, new InputSource(reader));
|
||||
return mfact;
|
||||
}
|
||||
|
||||
protected static <T extends IsoMessage> void parseHeaders(
|
||||
final NodeList nodes, final MessageFactory<T> mfact) throws IOException {
|
||||
ArrayList<Element> refs = null;
|
||||
@@ -120,17 +122,23 @@ public class ConfigParser {
|
||||
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 = new ArrayList<>(nodes.getLength()-i);
|
||||
}
|
||||
refs.add(elem);
|
||||
}
|
||||
else throw new IOException("Invalid ISO8583 header element");
|
||||
} else {
|
||||
String header = elem.getChildNodes().item(0).getNodeValue();
|
||||
boolean binHeader = "true".equals(elem.getAttribute("binary"));
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Adding ISO8583 header for type {}: {}", elem.getAttribute("type"), header);
|
||||
log.trace("Adding {}ISO8583 header for type {}: {}",
|
||||
binHeader?"binary ":"", elem.getAttribute("type"), header);
|
||||
}
|
||||
if (binHeader) {
|
||||
mfact.setBinaryIsoHeader(type, HexCodec.hexDecode(header));
|
||||
} else {
|
||||
mfact.setIsoHeader(type, header);
|
||||
}
|
||||
mfact.setIsoHeader(type, header);
|
||||
}
|
||||
}
|
||||
if (refs != null) {
|
||||
@@ -148,7 +156,8 @@ public class ConfigParser {
|
||||
}
|
||||
String h = mfact.getIsoHeader(t2);
|
||||
if (h == null) {
|
||||
throw new IllegalArgumentException("Header def " + type + " refers to nonexistent header " + t2);
|
||||
throw new IllegalArgumentException("Header def " + type +
|
||||
" refers to nonexistent header " + t2);
|
||||
}
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Adding ISO8583 header for type {}: {} (copied from {})",
|
||||
@@ -171,7 +180,7 @@ public class ConfigParser {
|
||||
}
|
||||
if (elem.getAttribute("extends") != null && !elem.getAttribute("extends").isEmpty()) {
|
||||
if (subs == null) {
|
||||
subs = new ArrayList<Element>(nodes.getLength()-i);
|
||||
subs = new ArrayList<>(nodes.getLength()-i);
|
||||
}
|
||||
subs.add(elem);
|
||||
continue;
|
||||
@@ -183,15 +192,14 @@ public class ConfigParser {
|
||||
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"));
|
||||
if (f.getParentNode()==elem) {
|
||||
final int num = Integer.parseInt(f.getAttribute("num"));
|
||||
IsoValue<?> v = getTemplateField(f, mfact, true);
|
||||
if (v != null) {
|
||||
v.setCharacterEncoding(mfact.getCharacterEncoding());
|
||||
}
|
||||
m.setField(num, v);
|
||||
}
|
||||
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);
|
||||
}
|
||||
@@ -204,7 +212,7 @@ public class ConfigParser {
|
||||
+ elem.getAttribute("type") + " extends invalid template "
|
||||
+ elem.getAttribute("extends"));
|
||||
}
|
||||
T tref = mfact.getMessageTemplate(ref);
|
||||
IsoMessage tref = mfact.getMessageTemplate(ref);
|
||||
if (tref == null) {
|
||||
throw new IllegalArgumentException("Message template "
|
||||
+ elem.getAttribute("type") + " extends nonexistent template "
|
||||
@@ -223,18 +231,12 @@ public class ConfigParser {
|
||||
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"));
|
||||
if (f.getParentNode()==elem) {
|
||||
IsoValue<?> v = getTemplateField(f, mfact, true);
|
||||
if (v != null) {
|
||||
v.setCharacterEncoding(mfact.getCharacterEncoding());
|
||||
}
|
||||
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);
|
||||
m.setField(num, v);
|
||||
}
|
||||
}
|
||||
mfact.addMessageTemplate(m);
|
||||
@@ -242,6 +244,53 @@ public class ConfigParser {
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates an IsoValue from the XML definition in a message template.
|
||||
* If it's for a toplevel field and the message factory has a codec for this field,
|
||||
* that codec is assigned to that field. For nested fields, a CompositeField is
|
||||
* created and populated. */
|
||||
protected static <M extends IsoMessage> IsoValue<?> getTemplateField(
|
||||
Element f, MessageFactory<M> mfact, boolean toplevel) {
|
||||
final int num = Integer.parseInt(f.getAttribute("num"));
|
||||
final String typedef = f.getAttribute("type");
|
||||
if ("exclude".equals(typedef)) {
|
||||
return null;
|
||||
}
|
||||
int length = 0;
|
||||
if (f.getAttribute("length").length() > 0) {
|
||||
length = Integer.parseInt(f.getAttribute("length"));
|
||||
}
|
||||
final IsoType itype = IsoType.valueOf(typedef);
|
||||
final NodeList subs = f.getElementsByTagName("field");
|
||||
if (subs != null && subs.getLength() > 0) {
|
||||
//Composite field
|
||||
final CompositeField cf = new CompositeField();
|
||||
for (int j = 0; j < subs.getLength(); j++) {
|
||||
Element sub = (Element)subs.item(j);
|
||||
if (sub.getParentNode()==f) {
|
||||
IsoValue<?> sv = getTemplateField(sub, mfact, false);
|
||||
if (sv != null) {
|
||||
sv.setCharacterEncoding(mfact.getCharacterEncoding());
|
||||
cf.addValue(sv);
|
||||
}
|
||||
}
|
||||
}
|
||||
return itype.needsLength() ? new IsoValue<>(itype, cf, length, cf) :
|
||||
new IsoValue<>(itype, cf, cf);
|
||||
}
|
||||
final String v;
|
||||
if (f.getChildNodes().getLength() == 0) {
|
||||
v = "";
|
||||
} else {
|
||||
v = f.getChildNodes().item(0).getNodeValue();
|
||||
}
|
||||
final CustomField<Object> cf = toplevel ? mfact.getCustomField(num) : null;
|
||||
if (cf == null) {
|
||||
return itype.needsLength() ? new IsoValue<>(itype, v, length) : new IsoValue<>(itype, v);
|
||||
}
|
||||
return itype.needsLength() ? new IsoValue<>(itype, cf.decodeField(v), length, cf) :
|
||||
new IsoValue<>(itype, cf.decodeField(v), cf);
|
||||
}
|
||||
|
||||
protected static <T extends IsoMessage> FieldParseInfo getParser(
|
||||
Element f, MessageFactory<T> mfact) throws IOException {
|
||||
IsoType itype = IsoType.valueOf(f.getAttribute("type"));
|
||||
@@ -267,7 +316,7 @@ public class ConfigParser {
|
||||
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>>();
|
||||
HashMap<Integer,HashMap<Integer,FieldParseInfo>> guides = new HashMap<>();
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
Element elem = (Element)nodes.item(i);
|
||||
int type = parseType(elem.getAttribute("type"));
|
||||
@@ -276,12 +325,12 @@ public class ConfigParser {
|
||||
}
|
||||
if (elem.getAttribute("extends") != null && !elem.getAttribute("extends").isEmpty()) {
|
||||
if (subs == null) {
|
||||
subs = new ArrayList<Element>(nodes.getLength()-i);
|
||||
subs = new ArrayList<>(nodes.getLength()-i);
|
||||
}
|
||||
subs.add(elem);
|
||||
continue;
|
||||
}
|
||||
HashMap<Integer, FieldParseInfo> parseMap = new HashMap<Integer, FieldParseInfo>();
|
||||
HashMap<Integer, FieldParseInfo> parseMap = new HashMap<>();
|
||||
NodeList fields = elem.getElementsByTagName("field");
|
||||
for (int j = 0; j < fields.getLength(); j++) {
|
||||
Element f = (Element)fields.item(j);
|
||||
@@ -308,11 +357,10 @@ public class ConfigParser {
|
||||
+ elem.getAttribute("type") + " extends nonexistent guide "
|
||||
+ elem.getAttribute("extends"));
|
||||
}
|
||||
HashMap<Integer,FieldParseInfo> child = new HashMap<Integer, FieldParseInfo>();
|
||||
HashMap<Integer,FieldParseInfo> child = new HashMap<>();
|
||||
child.putAll(parent);
|
||||
NodeList fields = elem.getElementsByTagName("field");
|
||||
for (int j = 0; j < fields.getLength(); j++) {
|
||||
Element f = (Element)fields.item(j);
|
||||
List<Element> fields = getDirectChildrenByTagName(elem, "field");
|
||||
for (Element f : fields) {
|
||||
int num = Integer.parseInt(f.getAttribute("num"));
|
||||
String typedef = f.getAttribute("type");
|
||||
if ("exclude".equals(typedef)) {
|
||||
@@ -322,15 +370,30 @@ public class ConfigParser {
|
||||
}
|
||||
}
|
||||
mfact.setParseMap(type, child);
|
||||
guides.put(type, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Element> getDirectChildrenByTagName(Element elem, String tagName) {
|
||||
List<Element> childElementsByTagName = new ArrayList<Element>();
|
||||
NodeList childNodes = elem.getChildNodes();
|
||||
for (int i = 0; i < childNodes.getLength(); i++) {
|
||||
if (childNodes.item(i).getNodeType() == Node.ELEMENT_NODE) {
|
||||
Element childElem = (Element)childNodes.item(i);
|
||||
if (childElem.getTagName().equals(tagName)) {
|
||||
childElementsByTagName.add(childElem);
|
||||
}
|
||||
}
|
||||
}
|
||||
return childElementsByTagName;
|
||||
}
|
||||
|
||||
/** 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. */
|
||||
* @param source The InputSource containing the XML configuration. */
|
||||
protected static <T extends IsoMessage> void parse(
|
||||
MessageFactory<T> mfact, InputStream stream) throws IOException {
|
||||
MessageFactory<T> mfact, InputSource source) throws IOException {
|
||||
final DocumentBuilderFactory docfact = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder docb = null;
|
||||
Document doc = null;
|
||||
@@ -351,13 +414,10 @@ public class ConfigParser {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
doc = docb.parse(stream);
|
||||
} catch (ParserConfigurationException ex) {
|
||||
doc = docb.parse(source);
|
||||
} catch (ParserConfigurationException | SAXException ex) {
|
||||
log.error("ISO8583 Cannot parse XML configuration", ex);
|
||||
return;
|
||||
} catch (SAXException ex) {
|
||||
log.error("ISO8583 Parsing XML configuration", ex);
|
||||
return;
|
||||
}
|
||||
final Element root = doc.getDocumentElement();
|
||||
|
||||
@@ -382,11 +442,8 @@ public class ConfigParser {
|
||||
* configure the message factory from that config. */
|
||||
public static <T extends IsoMessage> void configureFromUrl(
|
||||
MessageFactory<T> mfact, URL url) throws IOException {
|
||||
InputStream stream = url.openStream();
|
||||
try {
|
||||
parse(mfact, stream);
|
||||
} finally {
|
||||
stream.close();
|
||||
try (InputStream stream = url.openStream()) {
|
||||
parse(mfact, new InputSource(stream));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,19 +452,22 @@ public class ConfigParser {
|
||||
* Spring-bound instances of MessageFactory for example. */
|
||||
public static <T extends IsoMessage> void configureFromClasspathConfig(
|
||||
MessageFactory<T> mfact, String path) throws IOException {
|
||||
InputStream ins = mfact.getClass().getClassLoader().getResourceAsStream(path);
|
||||
if (ins != null) {
|
||||
log.debug("ISO8583 Parsing config from classpath file {}", path);
|
||||
try {
|
||||
parse(mfact, ins);
|
||||
} finally {
|
||||
ins.close();
|
||||
}
|
||||
} else {
|
||||
log.warn("ISO8583 File not found in classpath: {}", path);
|
||||
}
|
||||
try (InputStream ins = mfact.getClass().getClassLoader().getResourceAsStream(path)) {
|
||||
if (ins != null) {
|
||||
log.debug("ISO8583 Parsing config from classpath file {}", path);
|
||||
parse(mfact, new InputSource(ins));
|
||||
} else {
|
||||
log.warn("ISO8583 File not found in classpath: {}", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Configures a MessageFactory using the XML data obtained from the specified Reader. */
|
||||
public static <T extends IsoMessage> void configureFromReader(MessageFactory<T> mfact, Reader reader)
|
||||
throws IOException {
|
||||
parse(mfact, new InputSource(reader));
|
||||
}
|
||||
|
||||
/** Parses a message type expressed as a hex string and returns the integer number.
|
||||
* For example, "0200" or "200" return the number 512 (0x200) */
|
||||
private static int parseType(String type) throws IOException {
|
||||
|
||||
@@ -71,7 +71,7 @@ public class Date10ParseInfo extends DateTimeParseInfo {
|
||||
cal.setTimeZone(tz);
|
||||
}
|
||||
adjustWithFutureTolerance(cal);
|
||||
return new IsoValue<Date>(type, cal.getTime(), null);
|
||||
return new IsoValue<>(type, cal.getTime(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -105,7 +105,7 @@ public class Date10ParseInfo extends DateTimeParseInfo {
|
||||
cal.setTimeZone(tz);
|
||||
}
|
||||
adjustWithFutureTolerance(cal);
|
||||
return new IsoValue<Date>(type, cal.getTime(), null);
|
||||
return new IsoValue<>(type, cal.getTime(), null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.solab.iso8583.parse;
|
||||
|
||||
import com.solab.iso8583.CustomField;
|
||||
import com.solab.iso8583.IsoType;
|
||||
import com.solab.iso8583.IsoValue;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Blabla.
|
||||
*
|
||||
* @author Enrique Zamudio
|
||||
* Date: 06/01/16 10:52 AM
|
||||
*/
|
||||
public class Date12ParseInfo extends DateTimeParseInfo {
|
||||
|
||||
public Date12ParseInfo() {
|
||||
super(IsoType.DATE12, 12);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> IsoValue<Date> parse(final int field, final byte[] buf,
|
||||
final int pos, final CustomField<T> custom)
|
||||
throws ParseException, UnsupportedEncodingException {
|
||||
if (pos < 0) {
|
||||
throw new ParseException(String.format("Invalid DATE12 field %d position %d",
|
||||
field, pos), pos);
|
||||
}
|
||||
if (pos+12 > buf.length) {
|
||||
throw new ParseException(String.format("Insufficient data for DATE12 field %d, pos %d",
|
||||
field, pos), pos);
|
||||
}
|
||||
//A SimpleDateFormat in the case of dates won't help because of the missing data
|
||||
//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
|
||||
int year;
|
||||
if (forceStringDecoding) {
|
||||
year = Integer.parseInt(new String(buf, pos, 2, getCharacterEncoding()), 10);
|
||||
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 {
|
||||
year = ((buf[pos] - 48) * 10) + buf[pos + 1] - 48;
|
||||
cal.set(Calendar.MONTH, ((buf[pos+2] - 48) * 10) + buf[pos + 3] - 49);
|
||||
cal.set(Calendar.DATE, ((buf[pos + 4] - 48) * 10) + buf[pos + 5] - 48);
|
||||
cal.set(Calendar.HOUR_OF_DAY, ((buf[pos + 6] - 48) * 10) + buf[pos + 7] - 48);
|
||||
cal.set(Calendar.MINUTE, ((buf[pos + 8] - 48) * 10) + buf[pos + 9] - 48);
|
||||
cal.set(Calendar.SECOND, ((buf[pos + 10] - 48) * 10) + buf[pos + 11] - 48);
|
||||
}
|
||||
if (year > 50) {
|
||||
cal.set(Calendar.YEAR, 1900+year);
|
||||
} else {
|
||||
cal.set(Calendar.YEAR, 2000+year);
|
||||
}
|
||||
cal.set(Calendar.MILLISECOND,0);
|
||||
if (tz != null) {
|
||||
cal.setTimeZone(tz);
|
||||
}
|
||||
adjustWithFutureTolerance(cal);
|
||||
return new IsoValue<>(type, cal.getTime(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> IsoValue<Date> parseBinary(final int field, final byte[] buf,
|
||||
final int pos, final CustomField<T> custom)
|
||||
throws ParseException {
|
||||
if (pos < 0) {
|
||||
throw new ParseException(String.format("Invalid DATE12 field %d position %d",
|
||||
field, pos), pos);
|
||||
}
|
||||
if (pos+6 > buf.length) {
|
||||
throw new ParseException(String.format("Insufficient data for DATE12 field %d, pos %d",
|
||||
field, pos), pos);
|
||||
}
|
||||
int[] tens = new int[6];
|
||||
int start = 0;
|
||||
for (int i = pos; i < pos + tens.length; i++) {
|
||||
tens[start++] = (((buf[i] & 0xf0) >> 4) * 10) + (buf[i] & 0x0f);
|
||||
}
|
||||
Calendar cal = Calendar.getInstance();
|
||||
//A SimpleDateFormat in the case of dates won't help because of the missing data
|
||||
//we have to use the current date for reference and change what comes in the buffer
|
||||
//Set the month in the date
|
||||
if (tens[0] > 50) {
|
||||
cal.set(Calendar.YEAR, 1900+tens[0]);
|
||||
} else {
|
||||
cal.set(Calendar.YEAR, 2000+tens[0]);
|
||||
}
|
||||
cal.set(Calendar.MONTH, tens[1] - 1);
|
||||
cal.set(Calendar.DATE, tens[2]);
|
||||
cal.set(Calendar.HOUR_OF_DAY, tens[3]);
|
||||
cal.set(Calendar.MINUTE, tens[4]);
|
||||
cal.set(Calendar.SECOND, tens[5]);
|
||||
cal.set(Calendar.MILLISECOND,0);
|
||||
if (tz != null) {
|
||||
cal.setTimeZone(tz);
|
||||
}
|
||||
adjustWithFutureTolerance(cal);
|
||||
return new IsoValue<>(type, cal.getTime(), null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -66,7 +66,7 @@ public class Date4ParseInfo extends DateTimeParseInfo {
|
||||
cal.setTimeZone(tz);
|
||||
}
|
||||
Date10ParseInfo.adjustWithFutureTolerance(cal);
|
||||
return new IsoValue<Date>(type, cal.getTime(), null);
|
||||
return new IsoValue<>(type, cal.getTime(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,7 +94,7 @@ public class Date4ParseInfo extends DateTimeParseInfo {
|
||||
cal.setTimeZone(tz);
|
||||
}
|
||||
DateTimeParseInfo.adjustWithFutureTolerance(cal);
|
||||
return new IsoValue<Date>(type, cal.getTime(), null);
|
||||
return new IsoValue<>(type, cal.getTime(), null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ public class DateExpParseInfo extends DateTimeParseInfo {
|
||||
if (tz != null) {
|
||||
cal.setTimeZone(tz);
|
||||
}
|
||||
return new IsoValue<Date>(type, cal.getTime(), null);
|
||||
return new IsoValue<>(type, cal.getTime(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,6 +99,6 @@ public class DateExpParseInfo extends DateTimeParseInfo {
|
||||
if (tz != null) {
|
||||
cal.setTimeZone(tz);
|
||||
}
|
||||
return new IsoValue<Date>(type, cal.getTime(), null);
|
||||
return new IsoValue<>(type, cal.getTime(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,8 @@ public abstract class FieldParseInfo {
|
||||
fpi = new BinaryParseInfo(len);
|
||||
} else if (t == IsoType.DATE10) {
|
||||
fpi = new Date10ParseInfo();
|
||||
} else if (t == IsoType.DATE12) {
|
||||
fpi = new Date12ParseInfo();
|
||||
} else if (t == IsoType.DATE4) {
|
||||
fpi = new Date4ParseInfo();
|
||||
} else if (t == IsoType.DATE_EXP) {
|
||||
@@ -127,7 +129,11 @@ public abstract class FieldParseInfo {
|
||||
fpi = new NumericParseInfo(len);
|
||||
} else if (t == IsoType.TIME) {
|
||||
fpi = new TimeParseInfo();
|
||||
}
|
||||
} else if (t == IsoType.LLLLVAR) {
|
||||
fpi = new LlllvarParseInfo();
|
||||
} else if (t == IsoType.LLLLBIN) {
|
||||
fpi = new LlllbinParseInfo();
|
||||
}
|
||||
if (fpi == null) {
|
||||
throw new IllegalArgumentException(String.format("Cannot parse type %s", t));
|
||||
}
|
||||
@@ -138,11 +144,19 @@ 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), 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);
|
||||
switch(digits) {
|
||||
case 2:
|
||||
return ((buf[pos] - 48) * 10) + (buf[pos + 1] - 48);
|
||||
case 3:
|
||||
return ((buf[pos] - 48) * 100) + ((buf[pos + 1] - 48) * 10)
|
||||
+ (buf[pos + 2] - 48);
|
||||
case 4:
|
||||
return ((buf[pos] - 48) * 1000) + ((buf[pos + 1] - 48) * 100)
|
||||
+ ((buf[pos + 2] - 48) * 10) + (buf[pos + 3] - 48);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -62,12 +62,12 @@ public class LlbinParseInfo extends FieldParseInfo {
|
||||
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);
|
||||
return new IsoValue<>(type, binval, binval.length, null);
|
||||
} else if (custom instanceof CustomBinaryField) {
|
||||
try {
|
||||
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);
|
||||
return dec == null ? new IsoValue<>(type, binval, binval.length, null) :
|
||||
new IsoValue<>(type, dec, 0, custom);
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for LLBIN field %d, pos %d (LEN states '%s')",
|
||||
@@ -76,8 +76,8 @@ public class LlbinParseInfo extends FieldParseInfo {
|
||||
} else {
|
||||
try {
|
||||
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);
|
||||
return dec == null ? new IsoValue<>(type, binval, binval.length, null) :
|
||||
new IsoValue<>(type, dec, binval.length, custom);
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for LLBIN field %d, pos %d (LEN states '%s')",
|
||||
@@ -109,12 +109,12 @@ public class LlbinParseInfo extends FieldParseInfo {
|
||||
byte[] _v = new byte[l];
|
||||
System.arraycopy(buf, pos+1, _v, 0, l);
|
||||
if (custom == null) {
|
||||
return new IsoValue<byte[]>(type, _v, null);
|
||||
return new IsoValue<>(type, _v, null);
|
||||
} else if (custom instanceof CustomBinaryField) {
|
||||
try {
|
||||
T dec = ((CustomBinaryField<T>)custom).decodeBinaryField(buf, pos + 1, l);
|
||||
return dec == null ? new IsoValue<byte[]>(type, _v, _v.length, null) :
|
||||
new IsoValue<T>(type, dec, l, custom);
|
||||
return dec == null ? new IsoValue<>(type, _v, _v.length, null) :
|
||||
new IsoValue<>(type, dec, l, custom);
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for LLBIN field %d, pos %d length %d",
|
||||
@@ -122,8 +122,8 @@ public class LlbinParseInfo extends FieldParseInfo {
|
||||
}
|
||||
} else {
|
||||
T dec = custom.decodeField(HexCodec.hexEncode(_v, 0, _v.length));
|
||||
return dec == null ? new IsoValue<byte[]>(type, _v, null) :
|
||||
new IsoValue<T>(type, dec, custom);
|
||||
return dec == null ? new IsoValue<>(type, _v, null) :
|
||||
new IsoValue<>(type, dec, custom);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,13 +59,13 @@ public class LllbinParseInfo extends FieldParseInfo {
|
||||
}
|
||||
byte[] binval = l == 0 ? new byte[0] : HexCodec.hexDecode(new String(buf, pos + 3, l));
|
||||
if (custom == null) {
|
||||
return new IsoValue<byte[]>(type, binval, binval.length, null);
|
||||
return new IsoValue<>(type, binval, binval.length, null);
|
||||
} else if (custom instanceof CustomBinaryField) {
|
||||
try {
|
||||
T dec = ((CustomBinaryField<T>)custom).decodeBinaryField(
|
||||
buf, pos + 3, l);
|
||||
return dec == null ? new IsoValue<byte[]>(type, binval, binval.length, null) :
|
||||
new IsoValue<T>(type, dec, 0, custom);
|
||||
return dec == null ? new IsoValue<>(type, binval, binval.length, null) :
|
||||
new IsoValue<>(type, dec, 0, custom);
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for LLLBIN field %d, pos %d", field, pos), pos);
|
||||
@@ -74,8 +74,8 @@ public class LllbinParseInfo extends FieldParseInfo {
|
||||
try {
|
||||
T dec = custom.decodeField(
|
||||
l == 0 ? "" : new String(buf, pos + 3, l));
|
||||
return dec == null ? new IsoValue<byte[]>(type, binval, binval.length, null) :
|
||||
new IsoValue<T>(type, dec, l, custom);
|
||||
return dec == null ? new IsoValue<>(type, binval, binval.length, null) :
|
||||
new IsoValue<>(type, dec, l, custom);
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for LLLBIN field %d, pos %d", field, pos), pos);
|
||||
@@ -107,21 +107,21 @@ public class LllbinParseInfo extends FieldParseInfo {
|
||||
byte[] _v = new byte[l];
|
||||
System.arraycopy(buf, pos+2, _v, 0, l);
|
||||
if (custom == null) {
|
||||
return new IsoValue<byte[]>(type, _v, null);
|
||||
return new IsoValue<>(type, _v, null);
|
||||
} else if (custom instanceof CustomBinaryField) {
|
||||
try {
|
||||
T dec = ((CustomBinaryField<T>)custom).decodeBinaryField(
|
||||
buf, pos + 2, l);
|
||||
return dec == null ? new IsoValue<byte[]>(type, _v, _v.length, null) :
|
||||
new IsoValue<T>(type, dec, l, custom);
|
||||
return dec == null ? new IsoValue<>(type, _v, _v.length, null) :
|
||||
new IsoValue<>(type, dec, l, custom);
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for LLLBIN field %d, pos %d", field, pos), pos);
|
||||
}
|
||||
} else {
|
||||
T dec = custom.decodeField(HexCodec.hexEncode(_v, 0, _v.length));
|
||||
return dec == null ? new IsoValue<byte[]>(type, _v, null) :
|
||||
new IsoValue<T>(type, dec, custom);
|
||||
return dec == null ? new IsoValue<>(type, _v, null) :
|
||||
new IsoValue<>(type, dec, custom);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.solab.iso8583.parse;
|
||||
|
||||
import com.solab.iso8583.CustomBinaryField;
|
||||
import com.solab.iso8583.CustomField;
|
||||
import com.solab.iso8583.IsoType;
|
||||
import com.solab.iso8583.IsoValue;
|
||||
import com.solab.iso8583.util.HexCodec;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.text.ParseException;
|
||||
|
||||
/**
|
||||
* Blabla.
|
||||
*
|
||||
* @author Enrique Zamudio
|
||||
* Date: 19/02/15 18:29
|
||||
*/
|
||||
public class LlllbinParseInfo extends FieldParseInfo {
|
||||
|
||||
|
||||
public LlllbinParseInfo() {
|
||||
super(IsoType.LLLLBIN, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> IsoValue<?> parse(final int field, final byte[] buf,
|
||||
final int pos, final CustomField<T> custom)
|
||||
throws ParseException, UnsupportedEncodingException {
|
||||
if (pos < 0) {
|
||||
throw new ParseException(String.format("Invalid LLLLBIN field %d pos %d",
|
||||
field, pos), pos);
|
||||
} else if (pos+4 > buf.length) {
|
||||
throw new ParseException(String.format("Insufficient LLLLBIN header field %d",
|
||||
field), pos);
|
||||
}
|
||||
final int l = decodeLength(buf, pos, 4);
|
||||
if (l < 0) {
|
||||
throw new ParseException(String.format("Invalid LLLLBIN length %d field %d pos %d",
|
||||
l, field, pos), pos);
|
||||
} else if (l+pos+4 > buf.length) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for LLLLBIN field %d, pos %d", field, pos), pos);
|
||||
}
|
||||
byte[] binval = l == 0 ? new byte[0] : HexCodec.hexDecode(new String(buf, pos + 4, l));
|
||||
if (custom == null) {
|
||||
return new IsoValue<>(type, binval, binval.length, null);
|
||||
} else if (custom instanceof CustomBinaryField) {
|
||||
try {
|
||||
T dec = ((CustomBinaryField<T>)custom).decodeBinaryField(
|
||||
buf, pos + 4, l);
|
||||
return dec == null ? new IsoValue<>(type, binval, binval.length, null) :
|
||||
new IsoValue<>(type, dec, 0, custom);
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for LLLLBIN field %d, pos %d", field, pos), pos);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
T dec = custom.decodeField(
|
||||
l == 0 ? "" : new String(buf, pos + 4, l));
|
||||
return dec == null ? new IsoValue<>(type, binval, binval.length, null) :
|
||||
new IsoValue<>(type, dec, l, custom);
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for LLLLBIN field %d, pos %d", field, pos), pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> IsoValue<?> parseBinary(final int field, final byte[] buf,
|
||||
final int pos, final CustomField<T> custom)
|
||||
throws ParseException {
|
||||
if (pos < 0) {
|
||||
throw new ParseException(String.format("Invalid bin LLLLBIN field %d pos %d",
|
||||
field, pos), pos);
|
||||
} else if (pos+2 > buf.length) {
|
||||
throw new ParseException(String.format("Insufficient LLLLBIN header field %d",
|
||||
field), pos);
|
||||
}
|
||||
final int l = ((buf[pos] & 0xf0) * 1000) + ((buf[pos] & 0x0f) * 100)
|
||||
+ (((buf[pos + 1] & 0xf0) >> 4) * 10) + (buf[pos + 1] & 0x0f);
|
||||
if (l < 0) {
|
||||
throw new ParseException(String.format("Invalid LLLLBIN length %d field %d pos %d",
|
||||
l, field, pos), pos);
|
||||
}
|
||||
if (l+pos+2 > buf.length) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for bin LLLLBIN field %d, pos %d requires %d, only %d available",
|
||||
field, pos, l, buf.length-pos+1), pos);
|
||||
}
|
||||
byte[] _v = new byte[l];
|
||||
System.arraycopy(buf, pos+2, _v, 0, l);
|
||||
if (custom == null) {
|
||||
return new IsoValue<>(type, _v, null);
|
||||
} else if (custom instanceof CustomBinaryField) {
|
||||
try {
|
||||
T dec = ((CustomBinaryField<T>)custom).decodeBinaryField(
|
||||
buf, pos + 2, l);
|
||||
return dec == null ? new IsoValue<>(type, _v, _v.length, null) :
|
||||
new IsoValue<T>(type, dec, l, custom);
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for LLLLBIN field %d, pos %d", field, pos), pos);
|
||||
}
|
||||
} else {
|
||||
T dec = custom.decodeField(HexCodec.hexEncode(_v, 0, _v.length));
|
||||
return dec == null ? new IsoValue<>(type, _v, null) :
|
||||
new IsoValue<>(type, dec, custom);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.solab.iso8583.parse;
|
||||
|
||||
import com.solab.iso8583.CustomField;
|
||||
import com.solab.iso8583.IsoType;
|
||||
import com.solab.iso8583.IsoValue;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.text.ParseException;
|
||||
|
||||
/**
|
||||
* Blabla.
|
||||
*
|
||||
* @author Enrique Zamudio
|
||||
* Date: 19/02/15 18:30
|
||||
*/
|
||||
public class LlllvarParseInfo extends FieldParseInfo {
|
||||
|
||||
public LlllvarParseInfo() {
|
||||
super(IsoType.LLLLVAR, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> IsoValue<?> parse(final int field, final byte[] buf,
|
||||
final int pos, final CustomField<T> custom)
|
||||
throws ParseException, UnsupportedEncodingException {
|
||||
if (pos < 0) {
|
||||
throw new ParseException(String.format(
|
||||
"Invalid LLLLVAR field %d %d", field, pos), pos);
|
||||
} else if (pos+4 > buf.length) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for LLLLVAR header, pos %d", pos), pos);
|
||||
}
|
||||
final int len = decodeLength(buf, pos, 4);
|
||||
if (len < 0) {
|
||||
throw new ParseException(String.format(
|
||||
"Invalid LLLLVAR length %d, field %d pos %d", len, field, pos), pos);
|
||||
} else if (len+pos+4 > buf.length) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for LLLLVAR field %d, pos %d", field, pos), pos);
|
||||
}
|
||||
String _v;
|
||||
try {
|
||||
_v = len == 0 ? "" : new String(buf, pos + 4, len, getCharacterEncoding());
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for LLLLVAR 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() != len) {
|
||||
_v = new String(buf, pos + 4, buf.length-pos-4,
|
||||
getCharacterEncoding()).substring(0, len);
|
||||
}
|
||||
if (custom == null) {
|
||||
return new IsoValue<>(type, _v, len, null);
|
||||
} else {
|
||||
T dec = custom.decodeField(_v);
|
||||
return dec == null ? new IsoValue<>(type, _v, len, null) :
|
||||
new IsoValue<>(type, dec, len, custom);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> IsoValue<?> parseBinary(final int field, final byte[] buf,
|
||||
final int pos, final CustomField<T> custom)
|
||||
throws ParseException, UnsupportedEncodingException {
|
||||
if (pos < 0) {
|
||||
throw new ParseException(String.format("Invalid bin LLLLVAR field %d pos %d",
|
||||
field, pos), pos);
|
||||
} else if (pos+2 > buf.length) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for bin LLLLVAR header, field %d pos %d",
|
||||
field, pos), pos);
|
||||
}
|
||||
final int len = (((buf[pos] & 0xf0) >> 4) * 1000) + ((buf[pos] & 0x0f) * 100)
|
||||
+ (((buf[pos + 1] & 0xf0) >> 4) * 10) + (buf[pos + 1] & 0x0f);
|
||||
if (len < 0) {
|
||||
throw new ParseException(String.format(
|
||||
"Invalid bin LLLLVAR length %d, field %d pos %d", len, field, pos), pos);
|
||||
}
|
||||
if (len+pos+2 > buf.length) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for bin LLLLVAR field %d, pos %d", field, pos), pos);
|
||||
}
|
||||
if (custom == null) {
|
||||
return new IsoValue<>(type, new String(buf, pos + 2, len,
|
||||
getCharacterEncoding()), null);
|
||||
} else {
|
||||
T dec = custom.decodeField(new String(buf, pos + 2, len, getCharacterEncoding()));
|
||||
return dec == null ? new IsoValue<>(type,
|
||||
new String(buf, pos + 2, len, getCharacterEncoding()), null) :
|
||||
new IsoValue<>(type, dec, custom);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -69,12 +69,12 @@ public class LllvarParseInfo extends FieldParseInfo {
|
||||
getCharacterEncoding()).substring(0, len);
|
||||
}
|
||||
if (custom == null) {
|
||||
return new IsoValue<String>(type, _v, len, null);
|
||||
return new IsoValue<>(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, len, null) :
|
||||
new IsoValue<T>(type, decoded, len, custom);
|
||||
return decoded == null ? new IsoValue<>(type, _v, len, null) :
|
||||
new IsoValue<>(type, decoded, len, custom);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ public class LllvarParseInfo extends FieldParseInfo {
|
||||
throws ParseException, UnsupportedEncodingException {
|
||||
if (pos < 0) {
|
||||
throw new ParseException(String.format("Invalid bin LLLVAR field %d pos %d", field, pos), pos);
|
||||
} else if (pos+3 > buf.length) {
|
||||
} else if (pos+2 > buf.length) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for bin LLLVAR header, field %d pos %d", field, pos), pos);
|
||||
}
|
||||
@@ -96,12 +96,12 @@ public class LllvarParseInfo extends FieldParseInfo {
|
||||
"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, len, getCharacterEncoding()), null);
|
||||
return new IsoValue<>(type, new String(buf, pos + 2, len, getCharacterEncoding()), null);
|
||||
} else {
|
||||
IsoValue<T> v = new IsoValue<T>(type, custom.decodeField(
|
||||
IsoValue<T> v = new IsoValue<>(type, custom.decodeField(
|
||||
new String(buf, pos + 2, len, getCharacterEncoding())), custom);
|
||||
if (v.getValue() == null) {
|
||||
return new IsoValue<String>(type,
|
||||
return new IsoValue<>(type,
|
||||
new String(buf, pos + 2, len, getCharacterEncoding()), null);
|
||||
}
|
||||
return v;
|
||||
|
||||
@@ -70,11 +70,11 @@ public class LlvarParseInfo extends FieldParseInfo {
|
||||
getCharacterEncoding()).substring(0, len);
|
||||
}
|
||||
if (custom == null) {
|
||||
return new IsoValue<String>(type, _v, len, null);
|
||||
return new IsoValue<>(type, _v, len, null);
|
||||
} else {
|
||||
T dec = custom.decodeField(_v);
|
||||
return dec == null ? new IsoValue<String>(type, _v, len, null) :
|
||||
new IsoValue<T>(type, dec, len, custom);
|
||||
return dec == null ? new IsoValue<>(type, _v, len, null) :
|
||||
new IsoValue<>(type, dec, len, custom);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,13 +100,13 @@ public class LlvarParseInfo extends FieldParseInfo {
|
||||
"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, len,
|
||||
return new IsoValue<>(type, new String(buf, pos + 1, len,
|
||||
getCharacterEncoding()), null);
|
||||
} else {
|
||||
T dec = custom.decodeField(new String(buf, pos + 1, len, getCharacterEncoding()));
|
||||
return dec == null ? new IsoValue<String>(type,
|
||||
return dec == null ? new IsoValue<>(type,
|
||||
new String(buf, pos + 1, len, getCharacterEncoding()), null) :
|
||||
new IsoValue<T>(type, dec, custom);
|
||||
new IsoValue<>(type, dec, custom);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,12 @@ public final class Bcd {
|
||||
throws IndexOutOfBoundsException {
|
||||
char[] digits = new char[length];
|
||||
int start = 0;
|
||||
for (int i = pos; i < pos + (length / 2) + (length % 2); i++) {
|
||||
int i = pos;
|
||||
if (length % 2 != 0) {
|
||||
digits[start++] = (char)((buf[i] & 0x0f) + 48);
|
||||
i++;
|
||||
}
|
||||
for (;i < pos + (length / 2) + (length % 2); i++) {
|
||||
digits[start++] = (char)(((buf[i] & 0xf0) >> 4) + 48);
|
||||
digits[start++] = (char)((buf[i] & 0x0f) + 48);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<!ELEMENT header ( #PCDATA ) >
|
||||
<!ATTLIST header type NMTOKEN #REQUIRED >
|
||||
<!ATTLIST header ref NMTOKEN #IMPLIED >
|
||||
<!ATTLIST header binary ( true | false ) #IMPLIED >
|
||||
|
||||
<!ELEMENT template ( field* ) >
|
||||
<!ATTLIST template type NMTOKEN #REQUIRED >
|
||||
@@ -15,4 +16,4 @@
|
||||
<!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 | exclude) #REQUIRED >
|
||||
<!ATTLIST field type ( ALPHA | NUMERIC | AMOUNT | DATE10 | DATE4 | DATE_EXP | TIME | LLVAR | LLLVAR | BINARY | LLBIN | LLLBIN | LLLLVAR | LLLLBIN | exclude) #REQUIRED >
|
||||
|
||||
@@ -41,6 +41,6 @@ ISO header (optional)|Message Type|primary bitmap|secondary bitmap (optional)|da
|
||||
by IsoMessage objects, which store IsoValue instances for their data fields. You can work with the IsoValues
|
||||
or use the convenience methods of IsoMessage to work directly with the stored values.
|
||||
|
||||
j8583 can directly handle the basic data types: numeric values, strings, and dates. If you want to encode
|
||||
a custom object in a field, you can implement a custom field encoder. More information is available in the
|
||||
{{{guide.html}user's guide}}.
|
||||
j8583 can directly handle the basic data types: numeric values, strings, amounts, dates and byte arrays.
|
||||
If you want to encode a custom object in a field, you can implement a custom field encoder. More information
|
||||
is available in the {{{guide.html}user's guide}}.
|
||||
|
||||
@@ -17,8 +17,10 @@ Introduction to ISO8583
|
||||
*--------------*----------------*-----------------+
|
||||
| <<ISO type>> | <<j8583 type>> | <<Description >> |
|
||||
*--------------*----------------*-----------------+
|
||||
| Fixed-length | |
|
||||
| Numeric | NUMERIC | Fixed-width numeric values, padded with zeroes to the left.|
|
||||
*--------------*----------------*-----------------+
|
||||
| Fixed-length | |
|
||||
| Alphanumeric | ALPHA | Fixed-width alphanumeric values, padded with spaces to the right.|
|
||||
*--------------*----------------*-----------------+
|
||||
| Date | DATE10 | Date in format mmddHHMMSS, fixed width of 10. |
|
||||
@@ -35,6 +37,15 @@ Introduction to ISO8583
|
||||
*--------------*----------------*-----------------+
|
||||
| LLLVAR | LLLVAR | Variable-width alphanumeric value, up to 999 characters long. The length of the value is encoded in the first 3 characters of the value, for example "HEY" is encoded as 003HEY |
|
||||
*--------------*----------------*-----------------+
|
||||
| Fixed-length | | Similar to ALPHA, but stores byte arrays directly instead of text.
|
||||
| binary | BINARY |
|
||||
*--------------*----------------*-----------------+
|
||||
| Binary | LLBIN | Similar to LLVAR, but stores byte arrays directly instead of text.
|
||||
| LLVAR | |
|
||||
*--------------*----------------*-----------------+
|
||||
| Binary | LLLBIN | Similar to LLLVAR, but stores byte arrays directly instead of text.
|
||||
| LLLVAR | |
|
||||
*--------------*----------------*-----------------+
|
||||
ISO 8583 types and their corresponding j8583 types
|
||||
|
||||
* Common scenarios
|
||||
|
||||
@@ -61,7 +61,7 @@ message.setValue(4, amount, IsoType.AMOUNT, 12)
|
||||
.setValue(11, trace, IsoType.NUMERIC, 6);
|
||||
+-----
|
||||
|
||||
Of course it can be more succing in Groovy or Scala:
|
||||
Of course it can be more succint in Groovy or Scala:
|
||||
|
||||
+-----
|
||||
message.setField(4, IsoType.AMOUNT(amount))
|
||||
|
||||
@@ -12,7 +12,7 @@ Simple ISO8583 message parser
|
||||
j8583 configuration file. For example:
|
||||
|
||||
+-----
|
||||
java -cp lib/slf4j-api-1.7.1.jar:lib/slf4j-simple-1.7.1.jar:lib/j8583-1.5.6.jar com.solab.iso8583.util.SimpleParser /tmp/j8583-config.xml
|
||||
java -cp lib/slf4j-api-1.7.7.jar:lib/slf4j-simple-1.7.7.jar:lib/j8583-1.10.2.jar com.solab.iso8583.util.SimpleParser /tmp/j8583-config.xml
|
||||
+-----
|
||||
|
||||
You can paste an ISO8583 message encoded as text and the program will parse it, showing
|
||||
|
||||
+33
-16
@@ -11,23 +11,29 @@ Configuration via Spring
|
||||
|
||||
The properties that you want to set up in the Spring config are:
|
||||
|
||||
*------------+---------------------------------+
|
||||
| configPath | Specify the path to the XML configuration file with the message and parsing templates.
|
||||
*------------+---------------------------------+
|
||||
| customFields | A map with the CustomField implementations that should be used for each field (the keys are the field numbers).
|
||||
*------------+---------------------------------+
|
||||
| assignDate | Set it to true to have the MessageFactory set field 7 to a DATE10 with the date the message was created.
|
||||
*------------+---------------------------------+
|
||||
| etx | Set it to the numeric value of the message terminator, or -1 if you don't want to use a terminator.
|
||||
*------------+---------------------------------+
|
||||
| forceSecondaryBitmap | If true, then the messages will include the secondary bitmap even if they don't contain any fields above 64. Some providers require this.
|
||||
*------------+---------------------------------+
|
||||
*------------------------+---------------------------------+
|
||||
| configPath | Specify the path to the XML configuration file with the message and parsing templates.
|
||||
*------------------------+---------------------------------+
|
||||
| customFields | A map with the CustomField implementations that should be used for each field (the keys are the field numbers).
|
||||
*------------------------+---------------------------------+
|
||||
| characterEncoding | Sets the character encoding to use for parsing ALPHA, LLVAR and LLLVAR fields when it's different from the default platform encoding.
|
||||
*------------------------+---------------------------------+
|
||||
| assignDate | Set it to true to have the MessageFactory set field 7 to a DATE10 with the date the message was created.
|
||||
*------------------------+---------------------------------+
|
||||
| etx | Set it to the numeric value of the message terminator, or -1 if you don't want to use a terminator.
|
||||
*------------------------+---------------------------------+
|
||||
| forceSecondaryBitmap | If true, then the messages will include the secondary bitmap even if they don't contain any fields above 64. Some providers require this.
|
||||
*------------------------+---------------------------------+
|
||||
| forceStringEncoding | By default, text messages are encoded and decoded using the byte array directly, for performance, but this can cause problems when using an encoding other than the default or if a field contains non-ASCII characters. Set this flag to force proper string encoding/decoding with the character encoding that has been configured.
|
||||
*------------------------+---------------------------------+
|
||||
| ignoreLastMissingField | If true, then the factory only logs a warning when parsing incomplete messages. By default it will throw a ParseException.
|
||||
*------------+---------------------------------+
|
||||
| useBinaryMessages | Can be true or false (default false). The factory passes this value to new messages so that they're encoded in binary or ASCII.
|
||||
*------------+---------------------------------+
|
||||
| traceNumberGenerator | Set to an implementation of TraceNumberGenerator so that the factory sets a new trace value in field 11 on new messages.
|
||||
*------------+---------------------------------+
|
||||
*------------------------+---------------------------------+
|
||||
| traceNumberGenerator | Set to an implementation of TraceNumberGenerator so that the factory sets a new trace value in field 11 on new messages.
|
||||
*------------------------+---------------------------------+
|
||||
| useBinaryBitmap | Set this flag to make newly created messages encode their bitmap in binary format, even if the rest of the message is encoded in text. This only works for text messages and has no effect on binary messages.
|
||||
*------------------------+---------------------------------+
|
||||
| useBinaryMessages | Can be true or false (default false). The factory passes this value to new messages so that they're encoded in binary or ASCII.
|
||||
*------------------------+---------------------------------+
|
||||
|
||||
Example:
|
||||
|
||||
@@ -45,3 +51,14 @@ Configuration via Spring
|
||||
<property name="configPath" value="/your-j8583.xml" />
|
||||
</bean>
|
||||
+-----
|
||||
|
||||
There are some other properties than can only be set programmatically:
|
||||
|
||||
*------------------------+---------------------------------+
|
||||
| timezoneForParseGuide | Sets the timezone for a certain field of a specific message type. This is useful if you need to encode/decode dates with a timezone other than the local one.
|
||||
*------------------------+---------------------------------+
|
||||
| customField | Specifies a CustomField encoder to use in one field.
|
||||
*------------------------+---------------------------------+
|
||||
| isoHeader | Sets the ISO header to use for a specific message type.
|
||||
*------------------------+---------------------------------+
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ Encabezado ISO (opcional)|Tipo de mensaje|bitmap primario|bitmap secundario (opc
|
||||
clase IsoValue como valores en sus campos. Se puede trabajar directamente son los IsoValues o se pueden
|
||||
usar algunos métodos de conveniencia del IsoMessage para trabajar directamente con los datos almacenados.
|
||||
|
||||
j8583 puede manejar de forma directa los tipos de datos básicos: valores numéricos, cadenas, y fechas.
|
||||
j8583 puede manejar de forma directa los tipos de datos básicos: valores numéricos, cadenas, montos, fechas
|
||||
y arreglos de bytes.
|
||||
Si deseas guardar objetos de otra clase en un campo, puedes implementar tu propio codificador/decodificador
|
||||
de datos para ese campo. En la {{{guide.html}guía del usuario}} hay más información al respecto.
|
||||
|
||||
@@ -19,8 +19,12 @@ Introducción al protocolo ISO8583
|
||||
| <<Tipo ISO>> | <<Tipo j8583>> | <<Descripción>> |
|
||||
*--------------*----------------*-----------------+
|
||||
| Numérico | NUMERIC | Valores numéricos de longitud fija, rellenos con ceros a la izquierda.
|
||||
| de longitud | |
|
||||
| fija | |
|
||||
*--------------*----------------*-----------------+
|
||||
| Alfanumérico | ALPHA | Valores alfanuméricos de longitud fija, rellenos con espacios a la derecha.
|
||||
| de longitud | |
|
||||
| fija | |
|
||||
*--------------*----------------*-----------------+
|
||||
| Fecha | DATE10 | Fecha en formato mmddHHMMSS, con longitud fija de 10.
|
||||
*--------------*----------------*-----------------+
|
||||
@@ -35,7 +39,16 @@ Introducción al protocolo ISO8583
|
||||
| LLVAR | LLVAR | Valor alfanumérico de longitud variable, hasta 99 caracteres. La longitud del valor se codifica en los dos primeros caracteres del valor; por ejemplo "HOLA" se codifica como 04HOLA
|
||||
*--------------*----------------*-----------------+
|
||||
| LLLVAR | LLLVAR | Valor alfanumérico de longitud variable, hasta 999 caracteres. La longitud del valor se codifica en los dos primeros caracteres del valor; por ejemplo "HOLA" se codifica como 004HOLA
|
||||
*--------------*----------------*-----------------+
|
||||
*---------------*----------------*-----------------+
|
||||
| Binario de | | Similar al tipo ALPHA, pero maneja bytes directamente en vez de texto.
|
||||
| longitud fija | BINARY |
|
||||
*---------------*----------------*-----------------+
|
||||
| LLVAR | LLBIN | Similar al tipo LLVAR, pero maneja bytes directamente en vez de texto.
|
||||
| binario | |
|
||||
*---------------*----------------*-----------------+
|
||||
| LLLVAR | LLLBIN | Similar al tipo LLLVAR, pero maneja bytes directamente en vez de texto.
|
||||
| binario | |
|
||||
*---------------*----------------*-----------------+
|
||||
Tipos de ISO8583 y sus contrapartes en j8583
|
||||
|
||||
* Escenarios típicos
|
||||
|
||||
@@ -12,7 +12,7 @@ Procesador simple de mensajes ISO8583
|
||||
configuración de j8583. Por ejemplo:
|
||||
|
||||
+-----
|
||||
java -cp lib/slf4j-api-1.7.1.jar:lib/slf4j-simple-1.7.1.jar:lib/j8583-1.5.6.jar com.solab.iso8583.util.SimpleParser /tmp/j8583-config.xml
|
||||
java -cp lib/slf4j-api-1.7.7.jar:lib/slf4j-simple-1.7.7.jar:lib/j8583-1.10.2.jar com.solab.iso8583.util.SimpleParser /tmp/j8583-config.xml
|
||||
+-----
|
||||
|
||||
Una vez que arranca, se puede pegar un mensaje ISO8583 codificado en texto y el programa
|
||||
|
||||
+33
-16
@@ -11,23 +11,29 @@ Configurando j8583 con Spring
|
||||
|
||||
Las propiedades que se pueden configurar en la definición del componente en Spring son:
|
||||
|
||||
*------------+---------------------------------+
|
||||
| configPath | Indica la ruta al archivo de configuración XML que contiene las plantillas de mensajes y de lectura. Debe ser una ruta dentro del classpath.
|
||||
*------------+---------------------------------+
|
||||
| customFields | Un mapa con los CustomFields que se deben usar en cada campo que los requiera (las llaves son los números de campo).
|
||||
*------------+---------------------------------+
|
||||
| assignDate | Se pone en <true> para que la MessageFactory le ponga la fecha actual a los mensajes nuevos en el campo 7, tipo DATE10.
|
||||
*------------+---------------------------------+
|
||||
| etx | Se indica el valor numérico del caracter que debe ser el terminador de mensajes, o -1 para no usar un terminador.
|
||||
*------------+---------------------------------+
|
||||
| forceSecondaryBitmap | Si se activa, los mensajes creados incluirán el bitmap secundario en la trama generada aún cuando no tengan campos del 65 al 128. Algunos proveedores requieren esto.
|
||||
*------------+---------------------------------+
|
||||
*------------------------+---------------------------------+
|
||||
| configPath | Indica la ruta al archivo de configuración XML que contiene las plantillas de mensajes y de lectura. Debe ser una ruta dentro del classpath.
|
||||
*------------------------+---------------------------------+
|
||||
| customFields | Un mapa con los CustomFields que se deben usar en cada campo que los requiera (las llaves son los números de campo).
|
||||
*------------------------+---------------------------------+
|
||||
| characterEncoding | Fija el juego de caracteres que se debe usar para parsear datos de texto (ALPHA,LLVAR,LLLVAR) cuando se necesita uno distinto al valor por defecto de la plataforma.
|
||||
*------------------------+---------------------------------+
|
||||
| assignDate | Se pone en <true> para que la MessageFactory le ponga la fecha actual a los mensajes nuevos en el campo 7, tipo DATE10.
|
||||
*------------------------+---------------------------------+
|
||||
| etx | Se indica el valor numérico del caracter que debe ser el terminador de mensajes, o -1 para no usar un terminador.
|
||||
*------------------------+---------------------------------+
|
||||
| forceSecondaryBitmap | Si se activa, los mensajes creados incluirán el bitmap secundario en la trama generada aún cuando no tengan campos del 65 al 128. Algunos proveedores requieren esto.
|
||||
*------------------------+---------------------------------+
|
||||
| forceStringEncoding | Cuando se activa esta bandera, los valores de texto se decodifican utilizando el juego de caracteres definido, en vez de convertir directamente cadenas a partir de arreglos de bytes, lo cual es el comportamiento por defecto para un mejor desempeño pero puede presentar problemas con valores que contengan caracteres no-ASCII.
|
||||
*------------------------+---------------------------------+
|
||||
| ignoreLastMissingField | Si se activa esta propiedad, entonces al leer mensajes que se terminan antes de haber procesado todos los campos indicados en el bitmap, únicamente se imprime una advertencia en la bitácora. Normalmente (con la propiedad desactiva) se arroja una ParseException cuando faltan campos en el mensaje.
|
||||
*------------+---------------------------------+
|
||||
| useBinaryMessages | Si se activa esta propiedad, se pasa a los mensajes creados para que al generar sus tramas lo hagan en formato binario. El default es que se utilice ASCII.
|
||||
*------------+---------------------------------+
|
||||
| traceNumberGenerator | Se pasa la implementación de TraceNumberGenerator para que la fábrica pueda ponerle un nuevo número de traza en el campo 11 a los mensajes creados.
|
||||
*------------+---------------------------------+
|
||||
*------------------------+---------------------------------+
|
||||
| traceNumberGenerator | Se pasa la implementación de TraceNumberGenerator para que la fábrica pueda ponerle un nuevo número de traza en el campo 11 a los mensajes creados.
|
||||
*------------------------+---------------------------------+
|
||||
| useBinaryBitmap | Activar esta bandera indica que los mensajes nuevos deben codificar su bitmap en formato binario, aunque el resto se encuentre en formato texto. Esto no tiene efecto alguno en mensajes binarios.
|
||||
*------------------------+---------------------------------+
|
||||
| useBinaryMessages | Si se activa esta propiedad, se pasa a los mensajes creados para que al generar sus tramas lo hagan en formato binario. El default es que se utilice ASCII.
|
||||
*------------------------+---------------------------------+
|
||||
|
||||
Example:
|
||||
|
||||
@@ -45,3 +51,14 @@ Configurando j8583 con Spring
|
||||
<property name="configPath" value="/your-j8583.xml" />
|
||||
</bean>
|
||||
+-----
|
||||
|
||||
Hay algunas propiedades que únicamente se pueden modificar por código:
|
||||
|
||||
*------------------------+---------------------------------+
|
||||
| timezoneForParseGuide | Fija la zona horaria para un campo de un tipo de mensaje. Esto es útil para codificar o decodificar fechas y horas con una zona horario distinta de la local.
|
||||
*------------------------+---------------------------------+
|
||||
| customField | Especifica un CustomField a usar en un solo campo.
|
||||
*------------------------+---------------------------------+
|
||||
| isoHeader | Fija el encabezado ISO para un tipo de mensaje.
|
||||
*------------------------+---------------------------------+
|
||||
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
<item name="Download" href="http://sourceforge.net/projects/j8583/files/j8583/"/>
|
||||
</links>
|
||||
|
||||
<menu name="j8583 1.9.1">
|
||||
<menu name="j8583 1.10.2">
|
||||
<item name="Introduction" href="index.html"/>
|
||||
<item name="API docs" href="javadoc/index.html" />
|
||||
<item name="Beginner's Guide" href="guide.html"/>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<item name="Descargar" href="http://sourceforge.net/projects/j8583/files/j8583/"/>
|
||||
</links>
|
||||
|
||||
<menu name="j8583 1.9.1">
|
||||
<menu name="j8583 1.10.2">
|
||||
<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"/>
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.solab.iso8583;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.text.ParseException;
|
||||
|
||||
import org.junit.Assert;
|
||||
@@ -104,4 +105,23 @@ public class TestBinaries {
|
||||
Assert.assertEquals(sub2, HexCodec.hexEncode(sub1, 0, sub1.length));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test61() throws ParseException, UnsupportedEncodingException {
|
||||
BigInteger bignum = new BigInteger("1234512345123451234");
|
||||
IsoMessage iso1 = mfactBin.newMessage(0x201);
|
||||
iso1.setValue(3, bignum, IsoType.NUMERIC, 19);
|
||||
iso1.setField(7, null);
|
||||
byte[] buf = iso1.writeData();
|
||||
System.out.println(HexCodec.hexEncode(buf, 0, buf.length));
|
||||
IsoMessage iso2 = mfactBin.parseMessage(buf, 0);
|
||||
Assert.assertEquals(bignum, iso2.getObjectValue(3));
|
||||
bignum = new BigInteger("1234512345123451234522");
|
||||
iso1 = mfactBin.newMessage(0x202);
|
||||
iso1.setValue(3, bignum, IsoType.NUMERIC, 22);
|
||||
iso1.setField(7, null);
|
||||
buf = iso1.writeData();
|
||||
System.out.println(HexCodec.hexEncode(buf, 0, buf.length));
|
||||
iso2 = mfactBin.parseMessage(buf, 0);
|
||||
Assert.assertEquals(bignum, iso2.getObjectValue(3));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.solab.iso8583;
|
||||
|
||||
import com.solab.iso8583.codecs.CompositeField;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -14,10 +15,15 @@ import java.text.ParseException;
|
||||
*/
|
||||
public class TestConfigParser {
|
||||
|
||||
private MessageFactory<IsoMessage> config(String path) throws IOException {
|
||||
MessageFactory<IsoMessage> mfact = new MessageFactory<>();
|
||||
mfact.setConfigPath(path);
|
||||
return mfact;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParser() throws IOException, ParseException {
|
||||
MessageFactory<IsoMessage> mfact = new MessageFactory<IsoMessage>();
|
||||
mfact.setConfigPath("config.xml");
|
||||
final MessageFactory<IsoMessage> mfact = config("config.xml");
|
||||
//Headers
|
||||
Assert.assertNotNull(mfact.getIsoHeader(0x800));
|
||||
Assert.assertNotNull(mfact.getIsoHeader(0x810));
|
||||
@@ -27,7 +33,7 @@ public class TestConfigParser {
|
||||
Assert.assertNotNull(m200);
|
||||
final IsoMessage m400 = mfact.getMessageTemplate(0x400);
|
||||
Assert.assertNotNull(m400);
|
||||
for (int i=2; i <89; i++) {
|
||||
for (int i = 2; i < 89; i++) {
|
||||
IsoValue<?> v = m200.getField(i);
|
||||
if (v == null) {
|
||||
Assert.assertFalse(m400.hasField(i));
|
||||
@@ -58,4 +64,168 @@ public class TestConfigParser {
|
||||
Assert.assertTrue(m.hasField(39));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleCompositeParsers() throws IOException, ParseException {
|
||||
MessageFactory<IsoMessage> mfact = config("composites.xml");
|
||||
|
||||
IsoMessage m = mfact.parseMessage("01000040000000000000016one 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));
|
||||
|
||||
m = mfact.parseMessage("01000040000000000000018ALPHA05LLVAR12345X".getBytes(), 0);
|
||||
Assert.assertNotNull(m);
|
||||
Assert.assertTrue(m.hasField(10));
|
||||
f = m.getObjectValue(10);
|
||||
Assert.assertNotNull(f.getField(0));
|
||||
Assert.assertNotNull(f.getField(1));
|
||||
Assert.assertNotNull(f.getField(2));
|
||||
Assert.assertNotNull(f.getField(3));
|
||||
Assert.assertNull(f.getField(4));
|
||||
Assert.assertEquals("ALPHA", f.getObjectValue(0));
|
||||
Assert.assertEquals("LLVAR", f.getObjectValue(1));
|
||||
Assert.assertEquals("12345", f.getObjectValue(2));
|
||||
Assert.assertEquals("X", f.getObjectValue(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedCompositeParser() throws IOException, ParseException {
|
||||
MessageFactory<IsoMessage> mfact = config("composites.xml");
|
||||
IsoMessage m = mfact.parseMessage("01010040000000000000019ALPHA11F1F205F03F4X".getBytes(), 0);
|
||||
Assert.assertNotNull(m);
|
||||
Assert.assertTrue(m.hasField(10));
|
||||
CompositeField f = m.getObjectValue(10);
|
||||
Assert.assertNotNull(f.getField(0));
|
||||
Assert.assertNotNull(f.getField(1));
|
||||
Assert.assertNotNull(f.getField(2));
|
||||
Assert.assertNull(f.getField(3));
|
||||
Assert.assertEquals("ALPHA", f.getObjectValue(0));
|
||||
Assert.assertEquals("X", f.getObjectValue(2));
|
||||
f = f.getObjectValue(1);
|
||||
Assert.assertEquals("F1", f.getObjectValue(0));
|
||||
Assert.assertEquals("F2", f.getObjectValue(1));
|
||||
f = f.getObjectValue(2);
|
||||
Assert.assertEquals("F03", f.getObjectValue(0));
|
||||
Assert.assertEquals("F4", f.getObjectValue(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleCompositeTemplate() throws IOException {
|
||||
MessageFactory<IsoMessage> mfact = config("composites.xml");
|
||||
IsoMessage m = mfact.newMessage(0x100);
|
||||
//Simple composite
|
||||
Assert.assertNotNull(m);
|
||||
Assert.assertFalse(m.hasField(1));
|
||||
Assert.assertFalse(m.hasField(2));
|
||||
Assert.assertFalse(m.hasField(3));
|
||||
Assert.assertFalse(m.hasField(4));
|
||||
CompositeField f = m.getObjectValue(10);
|
||||
Assert.assertNotNull(f);
|
||||
Assert.assertEquals(f.getObjectValue(0), "abcde");
|
||||
Assert.assertEquals(f.getObjectValue(1), "llvar");
|
||||
Assert.assertEquals(f.getObjectValue(2), "12345");
|
||||
Assert.assertEquals(f.getObjectValue(3), "X");
|
||||
Assert.assertFalse(m.hasField(4));
|
||||
}
|
||||
|
||||
private void testNestedCompositeTemplate(int type, int fnum) throws IOException {
|
||||
MessageFactory<IsoMessage> mfact = config("composites.xml");
|
||||
IsoMessage m = mfact.newMessage(type);
|
||||
Assert.assertNotNull(m);
|
||||
Assert.assertFalse(m.hasField(1));
|
||||
Assert.assertFalse(m.hasField(2));
|
||||
Assert.assertFalse(m.hasField(3));
|
||||
Assert.assertFalse(m.hasField(4));
|
||||
CompositeField f = m.getObjectValue(fnum);
|
||||
Assert.assertEquals(f.getObjectValue(0), "fghij");
|
||||
Assert.assertEquals(f.getObjectValue(2), "67890");
|
||||
Assert.assertEquals(f.getObjectValue(3), "Y");
|
||||
f = f.getObjectValue(1);
|
||||
Assert.assertEquals(f.getObjectValue(0), "KL");
|
||||
Assert.assertEquals(f.getObjectValue(1), "mn");
|
||||
f = f.getObjectValue(2);
|
||||
Assert.assertEquals(f.getObjectValue(0), "123");
|
||||
Assert.assertEquals(f.getObjectValue(1), "45");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedCompositeTemplate() throws IOException {
|
||||
testNestedCompositeTemplate(0x101, 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedCompositeFromExtendedTemplate() throws IOException {
|
||||
testNestedCompositeTemplate(0x102, 10);
|
||||
testNestedCompositeTemplate(0x102, 12);
|
||||
}
|
||||
|
||||
@Test //issue 34
|
||||
public void testMultilevelExtendParseGuides() throws IOException, ParseException {
|
||||
final MessageFactory<IsoMessage> mfact = config("issue34.xml");
|
||||
//Parse a 200
|
||||
final String m200 = "0200422000000880800001X1231235959123456101010202020TERMINAL484";
|
||||
final String m210 = "0210422000000A80800001X123123595912345610101020202099TERMINAL484";
|
||||
final String m400 = "0400422000000880800401X1231235959123456101010202020TERMINAL484001X";
|
||||
final String m410 = "0410422000000a80800801X123123595912345610101020202099TERMINAL484001X";
|
||||
IsoMessage m = mfact.parseMessage(m200.getBytes(), 0);
|
||||
Assert.assertNotNull(m);
|
||||
Assert.assertEquals("X", m.getObjectValue(2));
|
||||
Assert.assertEquals("123456", m.getObjectValue(11));
|
||||
Assert.assertEquals("TERMINAL", m.getObjectValue(41));
|
||||
Assert.assertEquals("484", m.getObjectValue(49));
|
||||
m = mfact.parseMessage(m210.getBytes(), 0);
|
||||
Assert.assertNotNull(m);
|
||||
Assert.assertEquals("X", m.getObjectValue(2));
|
||||
Assert.assertEquals("123456", m.getObjectValue(11));
|
||||
Assert.assertEquals("TERMINAL", m.getObjectValue(41));
|
||||
Assert.assertEquals("484", m.getObjectValue(49));
|
||||
Assert.assertEquals("99", m.getObjectValue(39));
|
||||
m = mfact.parseMessage(m400.getBytes(), 0);
|
||||
Assert.assertNotNull(m);
|
||||
Assert.assertEquals("X", m.getObjectValue(2));
|
||||
Assert.assertEquals("123456", m.getObjectValue(11));
|
||||
Assert.assertEquals("TERMINAL", m.getObjectValue(41));
|
||||
Assert.assertEquals("484", m.getObjectValue(49));
|
||||
Assert.assertEquals("X", m.getObjectValue(62));
|
||||
m = mfact.parseMessage(m410.getBytes(), 0);
|
||||
Assert.assertNotNull(m);
|
||||
Assert.assertEquals("X", m.getObjectValue(2));
|
||||
Assert.assertEquals("123456", m.getObjectValue(11));
|
||||
Assert.assertEquals("TERMINAL", m.getObjectValue(41));
|
||||
Assert.assertEquals("484", m.getObjectValue(49));
|
||||
Assert.assertEquals("99", m.getObjectValue(39));
|
||||
Assert.assertEquals("X", m.getObjectValue(61));
|
||||
}
|
||||
|
||||
@Test // issue 47
|
||||
public void testExtendCompositeWithSameField() throws IOException, ParseException {
|
||||
final MessageFactory<IsoMessage> mfact = config("issue47.xml");
|
||||
|
||||
final String m200 = "02001000000000000004000000100000013ABCDEFGHIJKLM";
|
||||
IsoMessage isoMessage = mfact.parseMessage(m200.getBytes(), 0);
|
||||
|
||||
// check field num 4
|
||||
IsoValue<Object> field4 = isoMessage.getField(4);
|
||||
Assert.assertEquals(IsoType.AMOUNT, field4.getType());
|
||||
Assert.assertEquals(IsoType.AMOUNT.getLength(), field4.getLength());
|
||||
|
||||
// check nested field num 4 from composite field 62
|
||||
CompositeField compositeField62 = isoMessage.<CompositeField>getField(62).getValue();
|
||||
IsoValue<Object> nestedField4 = compositeField62.getField(0); // first in list
|
||||
Assert.assertEquals(IsoType.ALPHA, nestedField4.getType());
|
||||
Assert.assertEquals(13, nestedField4.getLength());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyFields() throws IOException, ParseException {
|
||||
final MessageFactory<IsoMessage> mfact = config("issue64.xml");
|
||||
IsoMessage msg = mfact.newMessage(0x200);
|
||||
Assert.assertEquals("", msg.getObjectValue(3));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,22 +17,25 @@ public class TestFormats {
|
||||
|
||||
@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");
|
||||
Assert.assertEquals("0125213456", IsoType.DATE10.format(date, null));
|
||||
Assert.assertEquals("0125", IsoType.DATE4.format(date, null));
|
||||
Assert.assertEquals("7301", IsoType.DATE_EXP.format(date, null));
|
||||
Assert.assertEquals("213456", IsoType.TIME.format(date, null));
|
||||
Assert.assertEquals("730125213456", IsoType.DATE12.format(date, null));
|
||||
//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));
|
||||
Assert.assertEquals("730126033456", IsoType.DATE12.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));
|
||||
Assert.assertEquals("730126043456", IsoType.DATE12.format(date, gmt));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -51,6 +54,7 @@ public class TestFormats {
|
||||
assert IsoType.ALPHA.format("hola", 6).equals("hola ");
|
||||
assert IsoType.LLVAR.format("hola", 0).equals("hola");
|
||||
assert IsoType.LLLVAR.format("hola", 0).equals("hola");
|
||||
assert IsoType.LLLLVAR.format("HOLA", 0).equals("HOLA");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.solab.iso8583;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Test ISO headers.
|
||||
*
|
||||
* @author Enrique Zamudio
|
||||
* Date: 01/07/16 8:21 AM
|
||||
*/
|
||||
public class TestHeaders {
|
||||
|
||||
private MessageFactory<IsoMessage> mf;
|
||||
|
||||
@Before
|
||||
public void init() throws IOException {
|
||||
mf = new MessageFactory<>();
|
||||
mf.setCharacterEncoding("UTF-8");
|
||||
mf.setConfigPath("config.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBinaryHeader() throws Exception {
|
||||
IsoMessage m = mf.newMessage(0x280);
|
||||
Assert.assertNotNull(m.getBinaryIsoHeader());
|
||||
byte[] buf = m.writeData();
|
||||
Assert.assertEquals(4+4+16+2, buf.length);
|
||||
for (int i=0; i < 4; i++) {
|
||||
Assert.assertEquals(buf[i], (byte)0xff);
|
||||
}
|
||||
Assert.assertEquals(buf[4], 0x30);
|
||||
Assert.assertEquals(buf[5], 0x32);
|
||||
Assert.assertEquals(buf[6], 0x38);
|
||||
Assert.assertEquals(buf[7], 0x30);
|
||||
//Then parse and check the header is binary 0xffffffff
|
||||
m = mf.parseMessage(buf, 4, true);
|
||||
Assert.assertNull(m.getIsoHeader());
|
||||
buf = m.getBinaryIsoHeader();
|
||||
Assert.assertNotNull(buf);
|
||||
for (int i=0; i < 4; i++) {
|
||||
Assert.assertEquals(buf[i], (byte)0xff);
|
||||
}
|
||||
Assert.assertEquals(0x280, m.getType());
|
||||
Assert.assertTrue(m.hasField(3));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public class TestIsoMessage {
|
||||
|
||||
@Before
|
||||
public void init() throws IOException {
|
||||
mf = new MessageFactory<IsoMessage>();
|
||||
mf = new MessageFactory<>();
|
||||
mf.setCharacterEncoding("UTF-8");
|
||||
mf.setCustomField(48, new CustomField48());
|
||||
mf.setConfigPath("config.xml");
|
||||
@@ -30,31 +30,35 @@ public class TestIsoMessage {
|
||||
@Test
|
||||
public void testCreation() {
|
||||
IsoMessage iso = mf.newMessage(0x200);
|
||||
assert iso.getType() == 0x200;
|
||||
assert iso.getField(3).getType() == IsoType.NUMERIC && "650000".equals(iso.getObjectValue(3));
|
||||
assert iso.getField(32).getType() == IsoType.LLVAR;
|
||||
assert iso.getField(35).getType() == IsoType.LLVAR;
|
||||
assert iso.getField(43).getType() == IsoType.ALPHA && ((String)iso.getObjectValue(43)).length() == 40;
|
||||
assert iso.getField(48).getType() == IsoType.LLLVAR && iso.getObjectValue(48) instanceof CustomField48;
|
||||
assert iso.getField(49).getType() == IsoType.ALPHA;
|
||||
assert iso.getField(60).getType() == IsoType.LLLVAR;
|
||||
assert iso.getField(61).getType() == IsoType.LLLVAR;
|
||||
assert iso.getField(100).getType() == IsoType.LLVAR;
|
||||
assert iso.getField(102).getType() == IsoType.LLVAR;
|
||||
Assert.assertEquals(0x200, iso.getType());
|
||||
Assert.assertTrue(iso.hasEveryField(3, 32, 35, 43, 48, 49, 60, 61, 100, 102));
|
||||
Assert.assertEquals(IsoType.NUMERIC, iso.getField(3).getType());
|
||||
Assert.assertEquals("650000", iso.getObjectValue(3));
|
||||
Assert.assertEquals(IsoType.LLVAR, iso.getField(32).getType());
|
||||
Assert.assertEquals(IsoType.LLVAR, iso.getField(35).getType());
|
||||
Assert.assertEquals(IsoType.ALPHA, iso.getField(43).getType());
|
||||
Assert.assertEquals(40, ((String) iso.getObjectValue(43)).length());
|
||||
Assert.assertEquals(IsoType.LLLVAR, iso.getField(48).getType());
|
||||
Assert.assertTrue(iso.getObjectValue(48) instanceof CustomField48);
|
||||
Assert.assertEquals(IsoType.ALPHA, iso.getField(49).getType());
|
||||
Assert.assertEquals(IsoType.LLLVAR, iso.getField(60).getType());
|
||||
Assert.assertEquals(IsoType.LLLVAR, iso.getField(61).getType());
|
||||
Assert.assertEquals(IsoType.LLVAR, iso.getField(100).getType());
|
||||
Assert.assertEquals(IsoType.LLVAR, iso.getField(102).getType());
|
||||
for (int i = 4; i < 32; i++) {
|
||||
assert !iso.hasField(i);
|
||||
Assert.assertFalse("ISO should not contain " + i, iso.hasField(i));
|
||||
}
|
||||
for (int i = 36; i < 43; i++) {
|
||||
assert !iso.hasField(i);
|
||||
Assert.assertFalse("ISO should not contain " + i, iso.hasField(i));
|
||||
}
|
||||
for (int i = 50; i < 60; i++) {
|
||||
assert !iso.hasField(i);
|
||||
Assert.assertFalse("ISO should not contain " + i, iso.hasField(i));
|
||||
}
|
||||
for (int i = 62; i < 100; i++) {
|
||||
assert !iso.hasField(i);
|
||||
Assert.assertFalse("ISO should not contain " + i, iso.hasField(i));
|
||||
}
|
||||
for (int i = 103; i < 128; i++) {
|
||||
assert !iso.hasField(i);
|
||||
Assert.assertFalse("ISO should not contain " + i, iso.hasField(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,14 +67,15 @@ public class TestIsoMessage {
|
||||
IsoMessage m1 = mf.newMessage(0x200);
|
||||
byte[] buf = m1.writeData();
|
||||
IsoMessage m2 = mf.parseMessage(buf, mf.getIsoHeader(0x200).length());
|
||||
assert m2.getType() == m1.getType();
|
||||
Assert.assertEquals(m2.getType(), m1.getType());
|
||||
for (int i = 2; i < 128; i++) {
|
||||
//Either both have the field or neither have it
|
||||
if (m1.hasField(i) && m2.hasField(i)) {
|
||||
Assert.assertEquals(m1.getField(i).getType(), m2.getField(i).getType());
|
||||
Assert.assertEquals(m1.getObjectValue(i), m2.getObjectValue(i));
|
||||
} else {
|
||||
assert !m1.hasField(i) && !m2.hasField(i);
|
||||
Assert.assertFalse(m1.hasField(i));
|
||||
Assert.assertFalse(m2.hasField(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,16 +113,16 @@ public class TestIsoMessage {
|
||||
public void testTemplating() {
|
||||
IsoMessage iso1 = mf.newMessage(0x200);
|
||||
IsoMessage iso2 = mf.newMessage(0x200);
|
||||
assert iso1 != iso2;
|
||||
assert iso1.getObjectValue(3).equals(iso2.getObjectValue(3));
|
||||
assert iso1.getField(3) != iso2.getField(3);
|
||||
assert iso1.getField(48) != iso2.getField(48);
|
||||
Assert.assertNotSame(iso1, iso2);
|
||||
Assert.assertEquals(iso1.getObjectValue(3), iso2.getObjectValue(3));
|
||||
Assert.assertNotSame(iso1.getField(3), iso2.getField(3));
|
||||
Assert.assertNotSame(iso1.getField(48), iso2.getField(48));
|
||||
CustomField48 cf48_1 = iso1.getObjectValue(48);
|
||||
int origv = cf48_1.getValue2();
|
||||
cf48_1.setValue2(origv + 1000);
|
||||
CustomField48 cf48_2 = iso2.getObjectValue(48);
|
||||
assert cf48_1 == cf48_2;
|
||||
assert cf48_2.getValue2() == origv + 1000;
|
||||
Assert.assertSame(cf48_1, cf48_2);
|
||||
Assert.assertEquals(cf48_2.getValue2(), origv + 1000);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@@ -125,12 +130,12 @@ public class TestIsoMessage {
|
||||
IsoMessage iso = mf.newMessage(0x200);
|
||||
IsoValue<String> f3 = iso.getField(3);
|
||||
iso.updateValue(3, "999999");
|
||||
assert iso.getObjectValue(3).equals("999999");
|
||||
Assert.assertEquals("999999", iso.getObjectValue(3));
|
||||
IsoValue<String> nf3 = iso.getField(3);
|
||||
assert f3 != nf3;
|
||||
assert f3.getType() == nf3.getType();
|
||||
assert f3.getLength() == nf3.getLength();
|
||||
assert f3.getEncoder() == nf3.getEncoder();
|
||||
Assert.assertNotSame(f3, nf3);
|
||||
Assert.assertEquals(f3.getType(), nf3.getType());
|
||||
Assert.assertEquals(f3.getLength(), nf3.getLength());
|
||||
Assert.assertSame(f3.getEncoder(), nf3.getEncoder());
|
||||
iso.updateValue(4, "INVALID!");
|
||||
throw new RuntimeException("Update failed!");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.solab.iso8583;
|
||||
|
||||
import com.solab.iso8583.util.HexCodec;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
|
||||
/**
|
||||
* Tests for LLLLVAR and LLLLBIN.
|
||||
*
|
||||
* @author Enrique Zamudio
|
||||
* Date: 19/02/15 18:25
|
||||
*/
|
||||
public class TestLlll {
|
||||
|
||||
private MessageFactory<IsoMessage> mfact = new MessageFactory<>();
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException {
|
||||
mfact.setConfigPath("issue36.xml");
|
||||
mfact.setAssignDate(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTemplate() {
|
||||
IsoMessage m = mfact.newMessage(0x100);
|
||||
Assert.assertEquals("010060000000000000000001X0002FF", m.debugString());
|
||||
m.setBinary(true);
|
||||
Assert.assertArrayEquals(new byte[]{1, 0, (byte) 0x60, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, (byte) 'X', 0, 1, (byte)0xff}, m.writeData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNewMessage() {
|
||||
IsoMessage m = mfact.newMessage(0x200);
|
||||
m.setValue(2, "Variable length text", IsoType.LLLLVAR, 0);
|
||||
m.setValue(3, "FFFF", IsoType.LLLLBIN, 0);
|
||||
Assert.assertEquals("020060000000000000000020Variable length text0004FFFF", m.debugString());
|
||||
m.setBinary(true);
|
||||
m.setValue(2, "XX", IsoType.LLLLVAR, 0);
|
||||
m.setValue(3, new byte[]{(byte) 0xff}, IsoType.LLLLBIN, 0);
|
||||
Assert.assertArrayEquals(new byte[]{2, 0, (byte) 0x60, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 2, (byte)'X', (byte)'X', 0, 1, (byte)0xff}, m.writeData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsing() throws ParseException, IOException {
|
||||
IsoMessage m = mfact.parseMessage("010060000000000000000001X0002FF".getBytes(), 0);
|
||||
Assert.assertNotNull(m);
|
||||
Assert.assertEquals("X", m.getObjectValue(2));
|
||||
Assert.assertArrayEquals(new byte[]{(byte) 0xff}, (byte[])m.getObjectValue(3));
|
||||
mfact.setUseBinaryMessages(true);
|
||||
m = mfact.parseMessage(new byte[]{1, 0, (byte) 0x60, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 2, (byte)'X', (byte)'X', 0, 1, (byte)0xff}, 0);
|
||||
Assert.assertNotNull(m);
|
||||
Assert.assertEquals("XX", m.getObjectValue(2));
|
||||
Assert.assertArrayEquals(new byte[]{(byte) 0xff}, (byte[])m.getObjectValue(3));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.solab.iso8583;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* Extended test for LLLLVar field
|
||||
*
|
||||
* @author Peter Margetiak
|
||||
*/
|
||||
public class TestLlllPatch {
|
||||
|
||||
private MessageFactory<IsoMessage> mfact = new MessageFactory<>();
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException {
|
||||
mfact.setConfigPath("issue50.xml");
|
||||
mfact.setAssignDate(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingLength() throws Exception {
|
||||
final int LENGTH = 1234;
|
||||
|
||||
// prepare
|
||||
String LLLLVar = makeLLLLVar(LENGTH);
|
||||
StringBuilder LLLLVarBuilder = new StringBuilder();
|
||||
LLLLVarBuilder.append("01004000000000000000")
|
||||
.append(String.format("%04d", LENGTH)).append(LLLLVar);
|
||||
|
||||
// parse
|
||||
IsoMessage m = mfact.parseMessage(LLLLVarBuilder.toString().getBytes(), 0);
|
||||
Assert.assertNotNull(m);
|
||||
Assert.assertEquals(LLLLVar, m.getObjectValue(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerialiseParseSmall() throws Exception {
|
||||
testSerialiseParse(88);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerialiseParseMedium() throws Exception {
|
||||
testSerialiseParse(258);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerialiseParseLarge() throws Exception {
|
||||
testSerialiseParse(9919);
|
||||
}
|
||||
|
||||
private void testSerialiseParse(final int LENGTH) throws Exception {
|
||||
// prepare
|
||||
String LLLLVar = makeLLLLVar(LENGTH);
|
||||
IsoMessage m = mfact.newMessage(0x100);
|
||||
m.setValue(2, LLLLVar, IsoType.LLLLVAR, 0);
|
||||
m.setBinary(true);
|
||||
|
||||
// write
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
m.write(bout, 2);
|
||||
bout.close();
|
||||
|
||||
// read
|
||||
byte[] buf = new byte[2];
|
||||
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
|
||||
bin.read(buf);
|
||||
Assert.assertNotEquals(buf, new byte[2]);
|
||||
|
||||
int len = ((buf[0] & 0xff) << 8) | (buf[1] & 0xff);
|
||||
buf = new byte[len];
|
||||
bin.read(buf);
|
||||
bin.close();
|
||||
|
||||
// parse
|
||||
mfact.setUseBinaryMessages(true);
|
||||
m = mfact.parseMessage(buf, mfact.getIsoHeader(0x100).length());
|
||||
Assert.assertNotNull(m);
|
||||
Assert.assertEquals(LLLLVar, m.getObjectValue(2));
|
||||
}
|
||||
|
||||
private String makeLLLLVar(final int length) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
sb.append("a");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -108,19 +108,6 @@ 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);
|
||||
@@ -132,6 +119,7 @@ public class TestParsing {
|
||||
Assert.assertEquals(Calendar.JANUARY, cal.get(Calendar.MONTH));
|
||||
Assert.assertEquals(25, cal.get(Calendar.DATE));
|
||||
Assert.assertEquals(21, cal.get(Calendar.HOUR_OF_DAY));
|
||||
Assert.assertEquals("debug string should match", "060002000000000000000125213456", m.debugString());
|
||||
mf.setTimezoneForParseGuide(0x600, 7, TimeZone.getTimeZone("GMT"));
|
||||
m = mf.parseMessage("060002000000000000000125213456".getBytes(), 0);
|
||||
f = m.getObjectValue(7);
|
||||
|
||||
@@ -21,7 +21,7 @@ public class TestComposites {
|
||||
@Test
|
||||
public void testEncodeText() {
|
||||
final CompositeField f = new CompositeField();
|
||||
f.addValue(new IsoValue<String>(IsoType.ALPHA, "One", 5));
|
||||
f.addValue(new IsoValue<>(IsoType.ALPHA, "One", 5));
|
||||
f.getValues().get(0).setCharacterEncoding("UTF-8");
|
||||
Assert.assertEquals("One ", f.encodeField(f));
|
||||
f.addValue("Two", null, IsoType.LLVAR, 0);
|
||||
@@ -37,13 +37,13 @@ public class TestComposites {
|
||||
@Test
|
||||
public void testEncodeBinary() {
|
||||
final CompositeField f = new CompositeField()
|
||||
.addValue(new IsoValue<String>(IsoType.ALPHA, "One", 5));
|
||||
.addValue(new IsoValue<>(IsoType.ALPHA, "One", 5));
|
||||
Assert.assertArrayEquals(new byte[]{'O', 'n', 'e', 32, 32}, f.encodeBinaryField(f));
|
||||
f.addValue(new IsoValue<String>(IsoType.LLVAR, "Two"));
|
||||
f.addValue(new IsoValue<>(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));
|
||||
f.addValue(new IsoValue<>(IsoType.NUMERIC, 999l, 5));
|
||||
f.addValue(new IsoValue<>(IsoType.ALPHA, "X", 1));
|
||||
Assert.assertArrayEquals(binaryData, f.encodeBinaryField(f));
|
||||
}
|
||||
|
||||
@@ -79,4 +79,19 @@ public class TestComposites {
|
||||
Assert.assertEquals("X", f.getValues().get(3).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeBinaryWithOffset() {
|
||||
final CompositeField dec = new CompositeField()
|
||||
.addParser(new LlvarParseInfo())
|
||||
.addParser(new NumericParseInfo(5))
|
||||
.addParser(new AlphaParseInfo(1));
|
||||
int offset = 5;
|
||||
final CompositeField f = dec.decodeBinaryField(binaryData, offset, this.binaryData.length - offset);
|
||||
Assert.assertNotNull(f);
|
||||
Assert.assertEquals(3, f.getValues().size());
|
||||
Assert.assertEquals("Two", f.getValues().get(0).getValue());
|
||||
Assert.assertEquals(999l, f.getValues().get(1).getValue());
|
||||
Assert.assertEquals("X", f.getValues().get(2).getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -48,4 +48,17 @@ public class TestDates {
|
||||
Assert.assertEquals(comp.getValue().getTime(), bin.getValue().getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDate12FutureTolerance() throws ParseException, IOException {
|
||||
Date soon = new Date(System.currentTimeMillis() + 50000);
|
||||
byte[] buf = IsoType.DATE12.format(soon, null).getBytes();
|
||||
IsoValue<Date> comp = new Date12ParseInfo().parse(0, buf, 0, null);
|
||||
assert comp.getValue().after(new Date());
|
||||
//Now with the binary
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
comp.write(bout, true, false);
|
||||
IsoValue<Date> bin = new Date12ParseInfo().parseBinary(0, bout.toByteArray(), 0, null);
|
||||
Assert.assertEquals(comp.getValue().getTime(), bin.getValue().getTime());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.solab.iso8583.parse;
|
||||
|
||||
import com.solab.iso8583.IsoType;
|
||||
import com.solab.iso8583.MessageFactory;
|
||||
import com.solab.iso8583.IsoMessage;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
|
||||
/**
|
||||
* Issue 38.
|
||||
*
|
||||
* @author Enrique Zamudio
|
||||
* Date: 26/03/15 10:54
|
||||
*/
|
||||
public class TestEmptyLvars {
|
||||
|
||||
private static MessageFactory<IsoMessage> txtfact = new MessageFactory<>();
|
||||
private static MessageFactory<IsoMessage> binfact = new MessageFactory<>();
|
||||
@BeforeClass
|
||||
public static void setupSpec() throws IOException {
|
||||
txtfact.setConfigPath("issue38.xml");
|
||||
binfact.setUseBinaryMessages(true);
|
||||
binfact.setConfigPath("issue38.xml");
|
||||
}
|
||||
|
||||
private void checkString(byte[] txt, byte[] bin, int field)
|
||||
throws IOException, ParseException {
|
||||
IsoMessage t = txtfact.parseMessage(txt, 0);
|
||||
IsoMessage b = binfact.parseMessage(bin, 0);
|
||||
Assert.assertTrue(t.hasField(field));
|
||||
Assert.assertTrue(b.hasField(field));
|
||||
Assert.assertTrue(((String) t.getObjectValue(field)).isEmpty());
|
||||
Assert.assertTrue(((String) b.getObjectValue(field)).isEmpty());
|
||||
}
|
||||
private void checkBin(byte[] txt, byte[] bin, int field)
|
||||
throws IOException, ParseException {
|
||||
IsoMessage t = txtfact.parseMessage(txt, 0);
|
||||
IsoMessage b = binfact.parseMessage(bin, 0);
|
||||
Assert.assertTrue(t.hasField(field));
|
||||
Assert.assertTrue(b.hasField(field));
|
||||
Assert.assertEquals(0, ((byte[]) t.getObjectValue(field)).length);
|
||||
Assert.assertEquals(0, ((byte[]) b.getObjectValue(field)).length);
|
||||
}
|
||||
@Test
|
||||
public void testEmptyLLVAR() throws Exception {
|
||||
IsoMessage t = txtfact.newMessage(0x100);
|
||||
IsoMessage b = binfact.newMessage(0x100);
|
||||
t.setValue(2, "", IsoType.LLVAR, 0);
|
||||
b.setValue(2, "", IsoType.LLVAR, 0);
|
||||
checkString(t.writeData(), b.writeData(), 2);
|
||||
}
|
||||
@Test
|
||||
public void testEmptyLLLVAR() throws Exception {
|
||||
IsoMessage t = txtfact.newMessage(0x100);
|
||||
IsoMessage b = binfact.newMessage(0x100);
|
||||
t.setValue(3, "", IsoType.LLLVAR, 0);
|
||||
b.setValue(3, "", IsoType.LLLVAR, 0);
|
||||
checkString(t.writeData(), b.writeData(), 3);
|
||||
}
|
||||
@Test
|
||||
public void testEmptyLLLLVAR() throws Exception {
|
||||
IsoMessage t = txtfact.newMessage(0x100);
|
||||
IsoMessage b = binfact.newMessage(0x100);
|
||||
t.setValue(4, "", IsoType.LLLLVAR, 0);
|
||||
b.setValue(4, "", IsoType.LLLLVAR, 0);
|
||||
checkString(t.writeData(), b.writeData(), 4);
|
||||
}
|
||||
@Test
|
||||
public void testEmptyLLBIN() throws Exception {
|
||||
IsoMessage t = txtfact.newMessage(0x100);
|
||||
IsoMessage b = binfact.newMessage(0x100);
|
||||
t.setValue(5, new byte[0], IsoType.LLBIN, 0);
|
||||
b.setValue(5, new byte[0], IsoType.LLBIN, 0);
|
||||
checkBin(t.writeData(), b.writeData(), 5);
|
||||
}
|
||||
@Test
|
||||
public void testEmptyLLLBIN() throws Exception {
|
||||
IsoMessage t = txtfact.newMessage(0x100);
|
||||
IsoMessage b = binfact.newMessage(0x100);
|
||||
t.setValue(6, new byte[0], IsoType.LLLBIN, 0);
|
||||
b.setValue(6, new byte[0], IsoType.LLLBIN, 0);
|
||||
checkBin(t.writeData(), b.writeData(), 6);
|
||||
}
|
||||
@Test
|
||||
public void testEmptyLLLLBIN() throws Exception {
|
||||
IsoMessage t = txtfact.newMessage(0x100);
|
||||
IsoMessage b = binfact.newMessage(0x100);
|
||||
t.setValue(7, new byte[0], IsoType.LLLLBIN, 0);
|
||||
b.setValue(7, new byte[0], IsoType.LLLLBIN, 0);
|
||||
checkBin(t.writeData(), b.writeData(), 7);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE j8583-config PUBLIC "-//J8583//DTD CONFIG 1.0//EN"
|
||||
"http://j8583.sourceforge.net/j8583.dtd">
|
||||
<j8583-config>
|
||||
<!-- This is a test config file -->
|
||||
|
||||
<template type="100">
|
||||
<field num="10" type="LLLVAR">
|
||||
<field num="1" type="ALPHA" length="5">abcde</field>
|
||||
<field num="2" type="LLVAR">llvar</field>
|
||||
<field num="3" type="NUMERIC" length="5">12345</field>
|
||||
<field num="4" type="ALPHA" length="1">X</field>
|
||||
</field>
|
||||
</template>
|
||||
|
||||
<template type="101">
|
||||
<field num="10" type="LLLVAR">
|
||||
<field num="1" type="ALPHA" length="5">fghij</field>
|
||||
<field num="2" type="LLVAR">
|
||||
<field num="1" type="ALPHA" length="2">KL</field>
|
||||
<field num="2" type="ALPHA" length="2">mn</field>
|
||||
<field num="3" type="LLVAR">
|
||||
<field num="1" type="ALPHA" length="3">123</field>
|
||||
<field num="2" type="ALPHA" length="2">45</field>
|
||||
</field>
|
||||
</field>
|
||||
<field num="3" type="NUMERIC" length="5">67890</field>
|
||||
<field num="4" type="ALPHA" length="1">Y</field>
|
||||
</field>
|
||||
</template>
|
||||
|
||||
<template type="102" extends="101">
|
||||
<field num="12" type="LLLVAR">
|
||||
<field num="1" type="ALPHA" length="5">fghij</field>
|
||||
<field num="2" type="LLVAR">
|
||||
<field num="1" type="ALPHA" length="2">KL</field>
|
||||
<field num="2" type="ALPHA" length="2">mn</field>
|
||||
<field num="3" type="LLVAR">
|
||||
<field num="1" type="ALPHA" length="3">123</field>
|
||||
<field num="2" type="ALPHA" length="2">45</field>
|
||||
</field>
|
||||
</field>
|
||||
<field num="3" type="NUMERIC" length="5">67890</field>
|
||||
<field num="4" type="ALPHA" length="1">Y</field>
|
||||
</field>
|
||||
</template>
|
||||
|
||||
<parse type="100">
|
||||
<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>
|
||||
|
||||
<parse type="101">
|
||||
<field num="10" type="LLLVAR">
|
||||
<field num="1" type="ALPHA" length="5" />
|
||||
<field num="2" type="LLVAR">
|
||||
<field num="1" type="ALPHA" length="2" />
|
||||
<field num="2" type="ALPHA" length="2" />
|
||||
<field num="3" type="LLVAR">
|
||||
<field num="1" type="ALPHA" length="3" />
|
||||
<field num="2" type="ALPHA" length="2" />
|
||||
</field>
|
||||
</field>
|
||||
<field num="4" type="ALPHA" length="1" />
|
||||
</field>
|
||||
</parse>
|
||||
|
||||
</j8583-config>
|
||||
@@ -11,6 +11,7 @@
|
||||
<header type="0410" ref="0210" />
|
||||
<header type="0800">ISO015000015</header>
|
||||
<header type="0810" ref="0800"/>
|
||||
<header type="0280" binary="true">ffffffff</header>
|
||||
|
||||
<!-- The client example uses this to create requests -->
|
||||
<template type="0200">
|
||||
@@ -54,6 +55,9 @@
|
||||
<field num="65" type="LLLBIN">01234567865</field>
|
||||
<field num="102" type="LLLVAR">Field of type LLLVAR</field>
|
||||
</template>
|
||||
<template type="0280">
|
||||
<field num="3" type="NUMERIC" length="2">99</field>
|
||||
</template>
|
||||
|
||||
<!-- The server example uses this to read the requests -->
|
||||
<parse type="0200">
|
||||
@@ -132,13 +136,14 @@
|
||||
<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 type="0201">
|
||||
<field num="3" type="NUMERIC" length="19" />
|
||||
</parse>
|
||||
<parse type="0202">
|
||||
<field num="3" type="NUMERIC" length="22" />
|
||||
</parse>
|
||||
<parse type="0280">
|
||||
<field num="3" type="NUMERIC" length="2" />
|
||||
</parse>
|
||||
|
||||
</j8583-config>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<j8583-config>
|
||||
<parse type="0200">
|
||||
<field num="2" type="LLVAR" />
|
||||
<field num="3" type="NUMERIC" length="6" />
|
||||
<field num="4" type="NUMERIC" length="12" />
|
||||
<field num="7" type="DATE10" />
|
||||
<field num="11" type="NUMERIC" length="6" />
|
||||
<field num="12" type="TIME" />
|
||||
<field num="13" type="DATE4" />
|
||||
<field num="15" type="DATE4" />
|
||||
<field num="18" type="NUMERIC" length="4"/>
|
||||
<field num="32" type="LLVAR" />
|
||||
<field num="37" type="NUMERIC" length="12"/>
|
||||
<field num="41" type="ALPHA" length="8"/>
|
||||
<field num="42" type="ALPHA" length="15"/>
|
||||
<field num="48" type="LLLVAR" />
|
||||
<field num="49" type="ALPHA" length="3"/>
|
||||
</parse>
|
||||
<parse type="0210" extends="0200">
|
||||
<field num="39" type="ALPHA" length="2" />
|
||||
<field num="62" type="LLLVAR" />
|
||||
</parse>
|
||||
|
||||
<parse type="0400" extends="0200">
|
||||
<field num="62" type="LLLVAR" />
|
||||
</parse>
|
||||
|
||||
<parse type="0410" extends="0400">
|
||||
<field num="39" type="ALPHA" length="2" />
|
||||
<field num="61" type="LLLVAR" />
|
||||
</parse>
|
||||
</j8583-config>
|
||||
@@ -0,0 +1,10 @@
|
||||
<j8583-config>
|
||||
<template type="0100">
|
||||
<field num="2" type="LLLLVAR">X</field>
|
||||
<field num="3" type="LLLLBIN">FF</field>
|
||||
</template>
|
||||
<parse type="0100">
|
||||
<field num="2" type="LLLLVAR" />
|
||||
<field num="3" type="LLLLBIN" />
|
||||
</parse>
|
||||
</j8583-config>
|
||||
@@ -0,0 +1,10 @@
|
||||
<j8583-config>
|
||||
<parse type="0100">
|
||||
<field num="2" type="LLVAR" />
|
||||
<field num="3" type="LLLVAR" />
|
||||
<field num="4" type="LLLLVAR" />
|
||||
<field num="5" type="LLBIN" />
|
||||
<field num="6" type="LLLBIN" />
|
||||
<field num="7" type="LLLLBIN" />
|
||||
</parse>
|
||||
</j8583-config>
|
||||
@@ -0,0 +1,14 @@
|
||||
<j8583-config>
|
||||
|
||||
<!-- Parent -->
|
||||
<parse type="000">
|
||||
<field num="4" type="AMOUNT" />
|
||||
</parse>
|
||||
|
||||
<parse type="0200" extends="000">
|
||||
<field num="62" type="LLLVAR">
|
||||
<field num="4" type="ALPHA" length="13" />
|
||||
</field>
|
||||
</parse>
|
||||
|
||||
</j8583-config>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<j8583-config>
|
||||
<header type="100">ISO999999999</header>
|
||||
<parse type="100">
|
||||
<field num="2" type="LLLLVAR" />
|
||||
</parse>
|
||||
</j8583-config>
|
||||
@@ -0,0 +1,7 @@
|
||||
<j8583-config>
|
||||
|
||||
<template type="0200">
|
||||
<field num="3" type="NUMERIC" length="6"></field>
|
||||
</template>
|
||||
|
||||
</j8583-config>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<Configuration>
|
||||
<Appenders>
|
||||
<Console name="console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="%d{ABSOLUTE} [%t] %-5p %m%n" />
|
||||
</Console>
|
||||
</Appenders>
|
||||
|
||||
<Loggers>
|
||||
<Logger name="io.netty" level="info"/>
|
||||
|
||||
<Root level="trace">
|
||||
<AppenderRef ref="console" />
|
||||
</Root>
|
||||
</Loggers>
|
||||
|
||||
</Configuration>
|
||||
Reference in New Issue
Block a user