Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecbc894f6b | ||
|
|
7c8931e9f4 | ||
|
|
f251343533 | ||
|
|
dd83ef7b96 | ||
|
|
e278903423 | ||
|
|
fa4006f270 | ||
|
|
1a60183268 | ||
|
|
e12f09430f | ||
|
|
00c046881b | ||
|
|
576dc50b44 | ||
|
|
2db47ed814 | ||
|
|
e310a859cb | ||
|
|
584aaeffc1 | ||
|
|
15a3d03766 | ||
|
|
1787dedfa1 | ||
|
|
143d14f07c | ||
|
|
34c19e26ec | ||
|
|
e74fb1b51c | ||
|
|
d974f6f85b | ||
|
|
eb1c0437d4 | ||
|
|
657b32ab10 | ||
|
|
a7561ce42b | ||
|
|
c57ad2c5ee | ||
|
|
6b7ec73e92 |
+7
-7
@@ -1,10 +1,10 @@
|
||||
apply plugin:'java'
|
||||
apply plugin: 'maven'
|
||||
defaultTasks 'build'
|
||||
version='1.10.1'
|
||||
version='1.11.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.6'
|
||||
compile 'org.slf4j:slf4j-api:1.7.7'
|
||||
testCompile 'junit:junit:4.11'
|
||||
testRuntime 'org.slf4j:slf4j-simple:1.7.6'
|
||||
testRuntime 'org.slf4j:slf4j-simple:1.7.7'
|
||||
}
|
||||
|
||||
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
|
||||
@@ -34,7 +34,7 @@ task sourcesJar(type:Jar) {
|
||||
classifier='sources'
|
||||
}
|
||||
|
||||
tasks.withType(Compile) { Compile compile ->
|
||||
tasks.withType(AbstractCompile) { AbstractCompile compile ->
|
||||
compile.options.debug = true
|
||||
compile.options.compilerArgs = ['-Xlint:all']
|
||||
}
|
||||
|
||||
@@ -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,9 +21,7 @@
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
<component name="CopyrightManager" default="">
|
||||
<module2copyright />
|
||||
</component>
|
||||
<component name="CopyrightManager" default="" />
|
||||
<component name="DependencyValidationManager">
|
||||
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
|
||||
</component>
|
||||
@@ -31,96 +29,10 @@
|
||||
<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>
|
||||
<option name="PROJECT_PROFILE" value="Project Default" />
|
||||
<option name="USE_PROJECT_PROFILE" value="true" />
|
||||
<version value="1.0" />
|
||||
</component>
|
||||
<component name="JavadocGenerationManager">
|
||||
<option name="OUTPUT_DIRECTORY" />
|
||||
<option name="OPTION_SCOPE" value="protected" />
|
||||
@@ -271,7 +183,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" 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 +207,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>
|
||||
|
||||
@@ -64,7 +64,8 @@ public class IsoMessage {
|
||||
}
|
||||
|
||||
/** 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;
|
||||
}
|
||||
@@ -124,7 +125,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 +207,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 +341,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) {
|
||||
@@ -458,6 +460,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);
|
||||
}
|
||||
@@ -496,4 +500,10 @@ public class IsoMessage {
|
||||
}
|
||||
}
|
||||
|
||||
/** Remove the specified fields from the message. */
|
||||
public void removeFields(int... idx) {
|
||||
for (int i : idx) {
|
||||
setField(i, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,11 @@ 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);
|
||||
|
||||
private boolean needsLen;
|
||||
private int length;
|
||||
@@ -110,7 +114,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 +152,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 +166,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 +182,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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -63,7 +63,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 +77,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 +100,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 +126,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 +134,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 +152,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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,7 +210,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 +221,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 +250,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 +264,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 % 1000) / 100) << 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 +290,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 +319,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;
|
||||
@@ -316,7 +340,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,25 @@ 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<>();
|
||||
/** 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 +125,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;
|
||||
}
|
||||
@@ -270,7 +272,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) {
|
||||
@@ -296,7 +298,8 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
if (buf.length < minlength) {
|
||||
throw new ParseException("Insufficient buffer length, needs to be at least " + minlength, 0);
|
||||
}
|
||||
final T m = createIsoMessage(isoHeaderLength > 0 ? new String(buf, 0, isoHeaderLength, encoding) : null);
|
||||
final T m = createIsoMessage(isoHeaderLength > 0 ?
|
||||
new String(buf, 0, isoHeaderLength, encoding) : null);
|
||||
m.setCharacterEncoding(encoding);
|
||||
final int type;
|
||||
if (useBinary) {
|
||||
@@ -440,17 +443,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.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 +483,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -554,7 +564,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);
|
||||
}
|
||||
@@ -576,10 +586,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,14 +41,14 @@ 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")
|
||||
@@ -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,7 +78,7 @@ 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());
|
||||
List<IsoValue> vals = new ArrayList<>(parsers.size());
|
||||
int pos = 0;
|
||||
try {
|
||||
for (FieldParseInfo fpi : parsers) {
|
||||
@@ -93,7 +93,8 @@ public class CompositeField implements CustomBinaryField<CompositeField> {
|
||||
}
|
||||
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,7 +115,7 @@ 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 {
|
||||
@@ -126,6 +127,8 @@ public class CompositeField implements CustomBinaryField<CompositeField> {
|
||||
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 +136,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 +158,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,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,29 +81,23 @@ 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, 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 {
|
||||
MessageFactory<IsoMessage> mfact = new MessageFactory<>();
|
||||
try (InputStream stream = url.openStream()) {
|
||||
parse(mfact, stream);
|
||||
} finally {
|
||||
stream.close();
|
||||
}
|
||||
return mfact;
|
||||
}
|
||||
@@ -120,7 +114,7 @@ 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);
|
||||
}
|
||||
@@ -148,7 +142,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 +166,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;
|
||||
@@ -267,7 +262,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 +271,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,7 +303,7 @@ 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++) {
|
||||
@@ -322,6 +317,7 @@ public class ConfigParser {
|
||||
}
|
||||
}
|
||||
mfact.setParseMap(type, child);
|
||||
guides.put(type, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,12 +348,9 @@ public class ConfigParser {
|
||||
}
|
||||
});
|
||||
doc = docb.parse(stream);
|
||||
} catch (ParserConfigurationException ex) {
|
||||
} 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 +375,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 {
|
||||
try (InputStream stream = url.openStream()) {
|
||||
parse(mfact, stream);
|
||||
} finally {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,17 +385,14 @@ 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, ins);
|
||||
} else {
|
||||
log.warn("ISO8583 File not found in classpath: {}", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Parses a message type expressed as a hex string and returns the integer number.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import com.solab.iso8583.IsoValue;
|
||||
public abstract class FieldParseInfo {
|
||||
|
||||
protected IsoType type;
|
||||
protected int length;
|
||||
protected final int length;
|
||||
private String encoding = System.getProperty("file.encoding");
|
||||
protected boolean forceStringDecoding;
|
||||
private CustomField<?> decoder;
|
||||
@@ -127,7 +127,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));
|
||||
}
|
||||
@@ -137,12 +141,20 @@ 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));
|
||||
} else if (digits == 3) {
|
||||
return ((buf[pos] - 48) * 100) + ((buf[pos + 1] - 48) * 10) + (buf[pos + 2] - 48);
|
||||
return Integer.parseInt(new String(buf, pos, digits, encoding), 10);
|
||||
} 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -49,24 +49,25 @@ public class LlbinParseInfo extends FieldParseInfo {
|
||||
throw new ParseException(String.format("Insufficient LLBIN header field %d",
|
||||
field), pos);
|
||||
}
|
||||
length = decodeLength(buf, pos, 2);
|
||||
if (length < 0) {
|
||||
final int len = decodeLength(buf, pos, 2);
|
||||
if (len < 0) {
|
||||
throw new ParseException(String.format("Invalid LLBIN field %d length %d pos %d",
|
||||
field, length, pos), pos);
|
||||
field, len, pos), pos);
|
||||
}
|
||||
if (length+pos+2 > buf.length) {
|
||||
if (len+pos+2 > buf.length) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for LLBIN field %d, pos %d (LEN states '%s')",
|
||||
field, pos, new String(buf, pos, 2)), pos);
|
||||
}
|
||||
byte[] binval = length == 0 ? new byte[0] : HexCodec.hexDecode(new String(buf, pos + 2, length));
|
||||
byte[] binval = len == 0 ? new byte[0] : HexCodec.hexDecode(
|
||||
new String(buf, pos + 2, len));
|
||||
if (custom == null) {
|
||||
return new IsoValue<byte[]>(type, binval, binval.length, null);
|
||||
return new IsoValue<>(type, binval, binval.length, null);
|
||||
} else if (custom instanceof CustomBinaryField) {
|
||||
try {
|
||||
T dec = ((CustomBinaryField<T>)custom).decodeBinaryField(buf, pos + 2, length);
|
||||
return dec == null ? new IsoValue<byte[]>(type, binval, binval.length, null) :
|
||||
new IsoValue<T>(type, dec, 0, custom);
|
||||
T dec = ((CustomBinaryField<T>)custom).decodeBinaryField(buf, pos + 2, len);
|
||||
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')",
|
||||
@@ -74,9 +75,9 @@ public class LlbinParseInfo extends FieldParseInfo {
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
T dec = custom.decodeField(new String(buf, pos + 2, length));
|
||||
return dec == null ? new IsoValue<byte[]>(type, binval, binval.length, null) :
|
||||
new IsoValue<T>(type, dec, binval.length, custom);
|
||||
T dec = custom.decodeField(new String(buf, pos + 2, len));
|
||||
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')",
|
||||
@@ -98,7 +99,7 @@ public class LlbinParseInfo extends FieldParseInfo {
|
||||
}
|
||||
final int l = (((buf[pos] & 0xf0) >> 4) * 10) + (buf[pos] & 0x0f);
|
||||
if (l < 0) {
|
||||
throw new ParseException(String.format("Invalid bin LLBIN length %d pos %d", length, pos), pos);
|
||||
throw new ParseException(String.format("Invalid bin LLBIN length %d pos %d", l, pos), pos);
|
||||
}
|
||||
if (l+pos+1 > buf.length) {
|
||||
throw new ParseException(String.format(
|
||||
@@ -108,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",
|
||||
@@ -121,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) * 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -46,34 +46,35 @@ public class LllvarParseInfo extends FieldParseInfo {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for LLLVAR header field %d pos %d", field, pos), pos);
|
||||
}
|
||||
length = decodeLength(buf, pos, 3);
|
||||
if (length < 0) {
|
||||
final int len = decodeLength(buf, pos, 3);
|
||||
if (len < 0) {
|
||||
throw new ParseException(String.format("Invalid LLLVAR length %d field %d pos %d",
|
||||
length, field, pos), pos);
|
||||
} else if (length+pos+3 > buf.length) {
|
||||
len, field, pos), pos);
|
||||
} else if (len+pos+3 > buf.length) {
|
||||
throw new ParseException(String.format("Insufficient data for LLLVAR field %d, pos %d",
|
||||
field, pos), pos);
|
||||
}
|
||||
String _v;
|
||||
try {
|
||||
_v = length == 0 ? "" : new String(buf, pos + 3, length, getCharacterEncoding());
|
||||
_v = len == 0 ? "" : new String(buf, pos + 3, len, getCharacterEncoding());
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for LLLVAR header, field %d pos %d", field, pos), pos);
|
||||
}
|
||||
//This is new: if the String's length is different from the specified length in the buffer,
|
||||
//there are probably some extended characters. So we create a String from the rest of the buffer,
|
||||
//and then cut it to the specified length.
|
||||
if (_v.length() != length) {
|
||||
_v = new String(buf, pos + 3, buf.length-pos-3, getCharacterEncoding()).substring(0, length);
|
||||
//This is new: if the String's length is different from the specified length in the
|
||||
//buffer, there are probably some extended characters. So we create a String from
|
||||
//the rest of the buffer, and then cut it to the specified length.
|
||||
if (_v.length() != len) {
|
||||
_v = new String(buf, pos + 3, buf.length-pos-3,
|
||||
getCharacterEncoding()).substring(0, len);
|
||||
}
|
||||
if (custom == null) {
|
||||
return new IsoValue<String>(type, _v, length, null);
|
||||
return new IsoValue<>(type, _v, len, null);
|
||||
} else {
|
||||
T decoded = custom.decodeField(_v);
|
||||
//If decode fails, return string; otherwise use the decoded object and its codec
|
||||
return decoded == null ? new IsoValue<String>(type, _v, length, null) :
|
||||
new IsoValue<T>(type, decoded, length, custom);
|
||||
return decoded == null ? new IsoValue<>(type, _v, len, null) :
|
||||
new IsoValue<>(type, decoded, len, custom);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,24 +83,26 @@ 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);
|
||||
}
|
||||
length = ((buf[pos] & 0x0f) * 100) + (((buf[pos + 1] & 0xf0) >> 4) * 10) + (buf[pos + 1] & 0x0f);
|
||||
if (length < 0) {
|
||||
final int len = ((buf[pos] & 0x0f) * 100) + (((buf[pos + 1] & 0xf0) >> 4) * 10) + (buf[pos + 1] & 0x0f);
|
||||
if (len < 0) {
|
||||
throw new ParseException(String.format(
|
||||
"Invalid bin LLLVAR length %d, field %d pos %d", length, field, pos), pos);
|
||||
} else if (length+pos+2 > buf.length) {
|
||||
"Invalid bin LLLVAR length %d, field %d pos %d", len, field, pos), pos);
|
||||
} else if (len+pos+2 > buf.length) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for bin LLLVAR field %d, pos %d", field, pos), pos);
|
||||
}
|
||||
if (custom == null) {
|
||||
return new IsoValue<String>(type, new String(buf, pos + 2, length, getCharacterEncoding()), null);
|
||||
return new IsoValue<>(type, new String(buf, pos + 2, len, getCharacterEncoding()), null);
|
||||
} else {
|
||||
IsoValue<T> v = new IsoValue<T>(type, custom.decodeField(new String(buf, pos + 2, length, getCharacterEncoding())), custom);
|
||||
IsoValue<T> v = new IsoValue<>(type, custom.decodeField(
|
||||
new String(buf, pos + 2, len, getCharacterEncoding())), custom);
|
||||
if (v.getValue() == null) {
|
||||
return new IsoValue<String>(type, new String(buf, pos + 2, length, getCharacterEncoding()), null);
|
||||
return new IsoValue<>(type,
|
||||
new String(buf, pos + 2, len, getCharacterEncoding()), null);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -40,37 +40,41 @@ public class LlvarParseInfo extends FieldParseInfo {
|
||||
final int pos, final CustomField<T> custom)
|
||||
throws ParseException, UnsupportedEncodingException {
|
||||
if (pos < 0) {
|
||||
throw new ParseException(String.format("Invalid LLVAR field %d %d", field, pos), pos);
|
||||
} else if (pos+2 > buf.length) {
|
||||
throw new ParseException(String.format("Insufficient data for LLVAR header, pos %d", pos), pos);
|
||||
}
|
||||
length = decodeLength(buf, pos, 2);
|
||||
if (length < 0) {
|
||||
throw new ParseException(String.format(
|
||||
"Invalid LLVAR length %d, field %d pos %d", length, field, pos), pos);
|
||||
} else if (length+pos+2 > buf.length) {
|
||||
"Invalid LLVAR field %d %d", field, pos), pos);
|
||||
} else if (pos+2 > buf.length) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for LLVAR header, pos %d", pos), pos);
|
||||
}
|
||||
final int len = decodeLength(buf, pos, 2);
|
||||
if (len < 0) {
|
||||
throw new ParseException(String.format(
|
||||
"Invalid LLVAR length %d, field %d pos %d", len, field, pos), pos);
|
||||
} else if (len+pos+2 > buf.length) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for LLVAR field %d, pos %d", field, pos), pos);
|
||||
}
|
||||
String _v;
|
||||
try {
|
||||
_v = length == 0 ? "" : new String(buf, pos + 2, length, getCharacterEncoding());
|
||||
_v = len == 0 ? "" : new String(buf, pos + 2, len, getCharacterEncoding());
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for LLVAR header, field %d pos %d", field, pos), pos);
|
||||
}
|
||||
//This is new: if the String's length is different from the specified length in the buffer,
|
||||
//there are probably some extended characters. So we create a String from the rest of the buffer,
|
||||
//and then cut it to the specified length.
|
||||
if (_v.length() != length) {
|
||||
_v = new String(buf, pos + 2, buf.length-pos-2, getCharacterEncoding()).substring(0, length);
|
||||
//This is new: if the String's length is different from the specified
|
||||
// length in the buffer, there are probably some extended characters.
|
||||
// So we create a String from the rest of the buffer, and then cut it to
|
||||
// the specified length.
|
||||
if (_v.length() != len) {
|
||||
_v = new String(buf, pos + 2, buf.length-pos-2,
|
||||
getCharacterEncoding()).substring(0, len);
|
||||
}
|
||||
if (custom == null) {
|
||||
return new IsoValue<String>(type, _v, length, null);
|
||||
return new IsoValue<>(type, _v, len, null);
|
||||
} else {
|
||||
T dec = custom.decodeField(_v);
|
||||
return dec == null ? new IsoValue<String>(type, _v, length, null) :
|
||||
new IsoValue<T>(type, dec, length, custom);
|
||||
return dec == null ? new IsoValue<>(type, _v, len, null) :
|
||||
new IsoValue<>(type, dec, len, custom);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,23 +87,26 @@ public class LlvarParseInfo extends FieldParseInfo {
|
||||
field, pos), pos);
|
||||
} else if (pos+1 > buf.length) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for bin LLVAR header, field %d pos %d", field, pos), pos);
|
||||
"Insufficient data for bin LLVAR header, field %d pos %d",
|
||||
field, pos), pos);
|
||||
}
|
||||
length = (((buf[pos] & 0xf0) >> 4) * 10) + (buf[pos] & 0x0f);
|
||||
if (length < 0) {
|
||||
final int len = (((buf[pos] & 0xf0) >> 4) * 10) + (buf[pos] & 0x0f);
|
||||
if (len < 0) {
|
||||
throw new ParseException(String.format(
|
||||
"Invalid bin LLVAR length %d, field %d pos %d", length, field, pos), pos);
|
||||
"Invalid bin LLVAR length %d, field %d pos %d", len, field, pos), pos);
|
||||
}
|
||||
if (length+pos+1 > buf.length) {
|
||||
if (len+pos+1 > buf.length) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for bin LLVAR field %d, pos %d", field, pos), pos);
|
||||
}
|
||||
if (custom == null) {
|
||||
return new IsoValue<String>(type, new String(buf, pos + 1, length, getCharacterEncoding()), null);
|
||||
return new IsoValue<>(type, new String(buf, pos + 1, len,
|
||||
getCharacterEncoding()), null);
|
||||
} else {
|
||||
T dec = custom.decodeField(new String(buf, pos + 1, length, getCharacterEncoding()));
|
||||
return dec == null ? new IsoValue<String>(type, new String(buf, pos + 1, length, getCharacterEncoding()), null) :
|
||||
new IsoValue<T>(type, dec, custom);
|
||||
T dec = custom.decodeField(new String(buf, pos + 1, len, getCharacterEncoding()));
|
||||
return dec == null ? new IsoValue<>(type,
|
||||
new String(buf, pos + 1, len, getCharacterEncoding()), null) :
|
||||
new IsoValue<>(type, dec, custom);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<!ATTLIST template type NMTOKEN #REQUIRED >
|
||||
<!ATTLIST template extends NMTOKEN #IMPLIED >
|
||||
|
||||
<!ELEMENT parse ( field+ ) >
|
||||
<!ELEMENT parse ( field* ) >
|
||||
<!ATTLIST parse type NMTOKEN #REQUIRED >
|
||||
<!ATTLIST parse extends NMTOKEN #IMPLIED >
|
||||
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -16,7 +16,7 @@ public class TestConfigParser {
|
||||
|
||||
@Test
|
||||
public void testParser() throws IOException, ParseException {
|
||||
MessageFactory<IsoMessage> mfact = new MessageFactory<IsoMessage>();
|
||||
MessageFactory<IsoMessage> mfact = new MessageFactory<>();
|
||||
mfact.setConfigPath("config.xml");
|
||||
//Headers
|
||||
Assert.assertNotNull(mfact.getIsoHeader(0x800));
|
||||
@@ -58,4 +58,42 @@ public class TestConfigParser {
|
||||
Assert.assertTrue(m.hasField(39));
|
||||
}
|
||||
|
||||
@Test //issue 34
|
||||
public void testMultilevelExtendParseGuides() throws IOException, ParseException {
|
||||
MessageFactory<IsoMessage> mfact = new MessageFactory<>();
|
||||
mfact.setConfigPath("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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,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,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,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,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>
|
||||
Reference in New Issue
Block a user