Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09c006fdc4 | ||
|
|
360cb3e3e5 | ||
|
|
ee8a4bca08 | ||
|
|
ce95be85e3 | ||
|
|
c19ff8ded1 | ||
|
|
c1bed18cb1 | ||
|
|
bc3c2b553b | ||
|
|
51da1fe599 | ||
|
|
d7cf38ffd5 | ||
|
|
f61826f064 | ||
|
|
2783dde094 | ||
|
|
70f2c89c3e | ||
|
|
774af5d220 | ||
|
|
69dd786f99 | ||
|
|
0506c39585 | ||
|
|
27f1331096 | ||
|
|
8db7571cb6 | ||
|
|
3e3fb61e3a | ||
|
|
c76d5e9b40 | ||
|
|
a84dacb9f3 | ||
|
|
e7306681eb | ||
|
|
b92bd46a0e | ||
|
|
a3bca5144a | ||
|
|
6e6cd45302 | ||
|
|
f42e9bf664 | ||
|
|
c18e56ce0c | ||
|
|
3f13f26b9d | ||
|
|
b5fdf63120 | ||
|
|
7694b5cc9c | ||
|
|
eeb8cb7ec1 | ||
|
|
d615114b33 | ||
|
|
c5f57b9eef | ||
|
|
abfd4f3e17 | ||
|
|
ea6896b767 | ||
|
|
992c737997 | ||
|
|
bec09e90fa | ||
|
|
188fcf5f3f | ||
|
|
505c8d89cb | ||
|
|
26f7746aae | ||
|
|
9746b00eca | ||
|
|
73c915ed95 | ||
|
|
b3215747c6 | ||
|
|
9ef99fd676 | ||
|
|
65f2360c51 | ||
|
|
4da4d6f9c9 | ||
|
|
21d8824045 | ||
|
|
3a32b90414 | ||
|
|
ab1d882476 | ||
|
|
a0c8413171 | ||
|
|
95a09942ff | ||
|
|
e3906fd4bc | ||
|
|
b84bf250b1 | ||
|
|
899154cbad | ||
|
|
09884e2f7d | ||
|
|
dabaac817a | ||
|
|
ea05c71410 | ||
|
|
fa82e83e22 | ||
|
|
450f073879 | ||
|
|
3df3235afa | ||
|
|
6f6652411c | ||
|
|
7608d7108f | ||
|
|
40a8c885db | ||
|
|
fcbc9c9ca5 | ||
|
|
21d25fb38a | ||
|
|
9e152cca33 | ||
|
|
8df997f101 | ||
|
|
3123eb4ea9 | ||
|
|
fa59763812 | ||
|
|
86d4cdb3ee | ||
|
|
6ed0ce0eb7 | ||
|
|
e8d914fe20 | ||
|
|
7d431f1f36 | ||
|
|
87680e7684 | ||
|
|
3f879523bc | ||
|
|
f582159495 | ||
|
|
bbee6601f9 | ||
|
|
e6444ebad3 | ||
|
|
eae946665b | ||
|
|
2ef2690df5 | ||
|
|
002eb48a50 | ||
|
|
d58b4a13a2 | ||
|
|
76aa3a11d3 | ||
|
|
28034fcd62 | ||
|
|
556a050575 | ||
|
|
bfc5c2bedc | ||
|
|
2970805a71 | ||
|
|
d751b1746e |
+4
-4
@@ -1,7 +1,7 @@
|
||||
apply plugin:'java'
|
||||
apply plugin: 'maven'
|
||||
defaultTasks 'build'
|
||||
version='1.11.0'
|
||||
version='1.13.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=7
|
||||
targetCompatibility=7
|
||||
@@ -17,9 +17,9 @@ configurations {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.slf4j:slf4j-api:1.7.7'
|
||||
testCompile 'junit:junit:4.11'
|
||||
testRuntime 'org.slf4j:slf4j-simple:1.7.7'
|
||||
compile 'org.slf4j:slf4j-api:1.7.21'
|
||||
testCompile 'junit:junit:4.12'
|
||||
testRuntime 'org.slf4j:slf4j-simple:1.7.21'
|
||||
}
|
||||
|
||||
tasks.javadoc.options.use=true
|
||||
|
||||
@@ -25,7 +25,9 @@
|
||||
<component name="DependencyValidationManager">
|
||||
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
|
||||
</component>
|
||||
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
|
||||
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false">
|
||||
<file url="PROJECT" charset="UTF-8" />
|
||||
</component>
|
||||
<component name="EntryPointsManager">
|
||||
<entry_points version="2.0" />
|
||||
</component>
|
||||
@@ -33,6 +35,24 @@
|
||||
<setting name="root" />
|
||||
</component>
|
||||
<component name="IdProvider" IDEtalkID="E31FCDAA3C23FC998F967D8CCAB8763A" />
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<option name="myLocal" value="true" />
|
||||
<inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="loggerClassName" value="org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.apache.commons.logging.LogFactory,java.util.logging.Logger" />
|
||||
<option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||
<option name="processCode" value="true" />
|
||||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
<option name="PROJECT_PROFILE" value="Project Default" />
|
||||
<option name="USE_PROJECT_PROFILE" value="true" />
|
||||
<version value="1.0" />
|
||||
</component>
|
||||
<component name="JavadocGenerationManager">
|
||||
<option name="OUTPUT_DIRECTORY" />
|
||||
<option name="OPTION_SCOPE" value="protected" />
|
||||
@@ -183,7 +203,7 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/j8583.iml" filepath="$PROJECT_DIR$/j8583.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" assert-keyword="true" jdk-15="true" project-jdk-name="Java 7" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="false" assert-keyword="true" jdk-15="true" project-jdk-name="Java 7" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
<component name="SvnConfiguration" maxAnnotateRevisions="500" myUseAcceleration="nothing" myAutoUpdateAfterCommit="false" cleanupOnStartRun="false">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>net.sf.j8583</groupId>
|
||||
<artifactId>j8583</artifactId>
|
||||
<version>1.10.2</version>
|
||||
<version>1.13.0</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>j8583</name>
|
||||
<description>Java implementation of the ISO 8583 protocol, focused on making the creation, edition and reading of ISO8583 messages as simple and flexible as possible.</description>
|
||||
@@ -27,15 +27,21 @@
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.7</version>
|
||||
<version>1.7.23</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.11</version>
|
||||
<version>4.12</version>
|
||||
<type>jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>1.7.23</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<organization>
|
||||
<name>Enrique Zamudio</name>
|
||||
@@ -60,10 +66,10 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>2.3.2</version>
|
||||
<version>3.5</version>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
|
||||
@@ -28,8 +28,8 @@ package com.solab.iso8583;
|
||||
*/
|
||||
public interface CustomBinaryField<T> extends CustomField<T> {
|
||||
|
||||
public T decodeBinaryField(byte[] value, int offset, int length);
|
||||
T decodeBinaryField(byte[] value, int offset, int length);
|
||||
|
||||
public byte[] encodeBinaryField(T value);
|
||||
byte[] encodeBinaryField(T value);
|
||||
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ package com.solab.iso8583;
|
||||
*/
|
||||
public interface CustomField<T> {
|
||||
|
||||
public T decodeField(String value);
|
||||
T decodeField(String value);
|
||||
|
||||
public String encodeField(T value);
|
||||
String encodeField(T value);
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
package com.solab.iso8583;
|
||||
|
||||
import com.solab.iso8583.util.HexCodec;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
@@ -47,6 +49,7 @@ public class IsoMessage {
|
||||
private IsoValue[] fields = new IsoValue[129];
|
||||
/** Stores the optional ISO header. */
|
||||
private String isoHeader;
|
||||
private byte[] binIsoHeader;
|
||||
private int etx = -1;
|
||||
/** Flag to enforce secondary bitmap even if empty. */
|
||||
private boolean forceb2;
|
||||
@@ -62,6 +65,10 @@ public class IsoMessage {
|
||||
protected IsoMessage(String header) {
|
||||
isoHeader = header;
|
||||
}
|
||||
/** Creates a new message with the specified binary ISO header. This will be prepended to the message. */
|
||||
protected IsoMessage(byte[] binaryHeader) {
|
||||
binIsoHeader = binaryHeader;
|
||||
}
|
||||
|
||||
/** Tells the message to encode its bitmap in binary format, even if the message
|
||||
* itself is encoded as text. This has no effect if the binary flag is set, which means
|
||||
@@ -109,13 +116,25 @@ public class IsoMessage {
|
||||
/** Sets the string to be sent as ISO header, that is, after the length header but before the message type.
|
||||
* This is useful in case an application needs some custom data in the ISO header of each message (very rare). */
|
||||
public void setIsoHeader(String value) {
|
||||
isoHeader = value;
|
||||
isoHeader = value;
|
||||
binIsoHeader = null;
|
||||
}
|
||||
/** Returns the ISO header that this message was created with. */
|
||||
public String getIsoHeader() {
|
||||
return isoHeader;
|
||||
}
|
||||
|
||||
/** Sets the string to be sent as ISO header, that is, after the length header but before the message type.
|
||||
* This is useful in case an application needs some custom data in the ISO header of each message (very rare). */
|
||||
public void setBinaryIsoHeader(byte[] binaryHeader) {
|
||||
isoHeader = null;
|
||||
binIsoHeader = binaryHeader;
|
||||
}
|
||||
/** Returns the binary ISO header that this message was created with. */
|
||||
public byte[] getBinaryIsoHeader() {
|
||||
return binIsoHeader;
|
||||
}
|
||||
|
||||
/** Sets the ISO message type. Common values are 0x200, 0x210, 0x400, 0x410, 0x800, 0x810. */
|
||||
public void setType(int value) {
|
||||
type = value;
|
||||
@@ -232,6 +251,7 @@ public class IsoMessage {
|
||||
} else {
|
||||
setValue(index, value, current.getEncoder(), current.getType(), current.getLength());
|
||||
getField(index).setCharacterEncoding(current.getCharacterEncoding());
|
||||
getField(index).setTimeZone(current.getTimeZone());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -350,7 +370,13 @@ public class IsoMessage {
|
||||
} catch (IOException ex) {
|
||||
//should never happen, writing to a ByteArrayOutputStream
|
||||
}
|
||||
}
|
||||
} else if (binIsoHeader != null) {
|
||||
try {
|
||||
bout.write(binIsoHeader);
|
||||
} catch (IOException ex) {
|
||||
//should never happen, writing to a ByteArrayOutputStream
|
||||
}
|
||||
}
|
||||
//Message Type
|
||||
if (binary) {
|
||||
bout.write((type & 0xff00) >> 8);
|
||||
@@ -431,6 +457,8 @@ public class IsoMessage {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (isoHeader != null) {
|
||||
sb.append(isoHeader);
|
||||
} else if (binIsoHeader != null) {
|
||||
sb.append("[0x").append(HexCodec.hexEncode(binIsoHeader, 0, binIsoHeader.length)).append("]");
|
||||
}
|
||||
sb.append(String.format("%04x", type));
|
||||
|
||||
@@ -506,4 +534,25 @@ public class IsoMessage {
|
||||
setField(i, null);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true is the message contains all the specified fields.
|
||||
* A convenience for m.hasField(x) && m.hasField(y) && m.hasField(z) && ... */
|
||||
public boolean hasEveryField(int... idx) {
|
||||
for (int i : idx) {
|
||||
if (!hasField(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/** Returns true is the message contains at least one of the specified fields.
|
||||
* A convenience for m.hasField(x) || m.hasField(y) || m.hasField(z) || ... */
|
||||
public boolean hasAnyField(int... idx) {
|
||||
for (int i : idx) {
|
||||
if (hasField(i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ public enum IsoType {
|
||||
LLVAR(false, 0),
|
||||
/** A variable length alphanumeric value with a 3-digit header length. */
|
||||
LLLVAR(false, 0),
|
||||
/** A date in format YYYYMMddHHmmss */
|
||||
DATE14(false, 14),
|
||||
/** A date in format MMddHHmmss */
|
||||
DATE10(false, 10),
|
||||
/** A date in format MMdd */
|
||||
@@ -60,7 +62,11 @@ public enum IsoType {
|
||||
/** variable length with 4-digit header length. */
|
||||
LLLLVAR(false, 0),
|
||||
/** variable length byte array with 4-digit header length. */
|
||||
LLLLBIN(false, 0);
|
||||
LLLLBIN(false, 0),
|
||||
/** Date in format yyMMddHHmmss. */
|
||||
DATE12(false,12),
|
||||
/** Date in format yyMMdd */
|
||||
DATE6(false,6);
|
||||
|
||||
private boolean needsLen;
|
||||
private int length;
|
||||
@@ -80,7 +86,7 @@ public enum IsoType {
|
||||
return length;
|
||||
}
|
||||
|
||||
/** Formats a Date if the receiver is DATE10, DATE4, DATE_EXP or TIME; throws an exception
|
||||
/** Formats a Date if the receiver is DATE10, DATE4, DATE_EXP, DATE12, DATE14 or TIME; throws an exception
|
||||
* otherwise. */
|
||||
public String format(final Date value, final TimeZone tz) {
|
||||
final SimpleDateFormat sdf;
|
||||
@@ -92,7 +98,13 @@ public enum IsoType {
|
||||
sdf = new SimpleDateFormat("yyMM");
|
||||
} else if (this == TIME) {
|
||||
sdf = new SimpleDateFormat("HHmmss");
|
||||
} else {
|
||||
} else if (this == DATE12) {
|
||||
sdf = new SimpleDateFormat("yyMMddHHmmss");
|
||||
} else if (this == DATE14) {
|
||||
sdf = new SimpleDateFormat("YYYYMMddHHmmss");
|
||||
} else if (this == DATE6) {
|
||||
sdf = new SimpleDateFormat("yyMMdd");
|
||||
} else {
|
||||
throw new IllegalArgumentException("Cannot format date as " + this);
|
||||
}
|
||||
if (tz != null) {
|
||||
|
||||
@@ -21,6 +21,7 @@ package com.solab.iso8583;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
@@ -199,10 +200,12 @@ public class IsoValue<T> implements Cloneable {
|
||||
if (type == IsoType.NUMERIC || type == IsoType.AMOUNT) {
|
||||
if (type == IsoType.AMOUNT) {
|
||||
if (value instanceof BigDecimal) {
|
||||
return type.format((BigDecimal)value, 12);
|
||||
return type.format((BigDecimal) value, 12);
|
||||
} else {
|
||||
return type.format(value.toString(), 12);
|
||||
}
|
||||
} else if (value instanceof BigInteger) {
|
||||
return type.format(encoder == null ? value.toString() : encoder.encodeField(value), length);
|
||||
} else if (value instanceof Number) {
|
||||
return type.format(((Number)value).longValue(), length);
|
||||
} else {
|
||||
@@ -277,7 +280,7 @@ public class IsoValue<T> implements Cloneable {
|
||||
}
|
||||
if (binary) {
|
||||
if (digits == 4) {
|
||||
outs.write((((l % 1000) / 100) << 4) | ((l % 1000)/100));
|
||||
outs.write((((l % 10000) / 1000) << 4) | ((l % 1000)/100));
|
||||
} else if (digits == 3) {
|
||||
outs.write(l / 100); //00 to 09 automatically in BCD
|
||||
}
|
||||
@@ -330,7 +333,9 @@ public class IsoValue<T> implements Cloneable {
|
||||
buf = new byte[(length / 2) + (length % 2)];
|
||||
} else if (type == IsoType.AMOUNT) {
|
||||
buf = new byte[6];
|
||||
} else if (type == IsoType.DATE10 || type == IsoType.DATE4 || type == IsoType.DATE_EXP || type == IsoType.TIME) {
|
||||
} else if (type == IsoType.DATE10 || type == IsoType.DATE4 ||
|
||||
type == IsoType.DATE_EXP || type == IsoType.TIME ||
|
||||
type == IsoType.DATE12 || type == IsoType.DATE14) {
|
||||
buf = new byte[length / 2];
|
||||
}
|
||||
//Encode in BCD if it's one of these types
|
||||
|
||||
@@ -58,6 +58,7 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
private TraceNumberGenerator traceGen;
|
||||
/** The ISO header to be included in each message type. */
|
||||
private Map<Integer, String> isoHeaders = new HashMap<>();
|
||||
private Map<Integer, byte[]> binIsoHeaders = new HashMap<>();
|
||||
/** A map for the custom field encoder/decoders, keyed by field number. */
|
||||
@SuppressWarnings("rawtypes")
|
||||
private Map<Integer, CustomField> customFields = new HashMap<>();
|
||||
@@ -211,7 +212,12 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
* messages, then the returned message will be written using binary coding.
|
||||
* @param type The message type, for example 0x200, 0x400, etc. */
|
||||
public T newMessage(int type) {
|
||||
T m = createIsoMessage(isoHeaders.get(type));
|
||||
T m;
|
||||
if (binIsoHeaders.get(type) != null) {
|
||||
m = createIsoMessageWithBinaryHeader(binIsoHeaders.get(type));
|
||||
} else {
|
||||
m = createIsoMessage(isoHeaders.get(type));
|
||||
}
|
||||
m.setType(type);
|
||||
m.setEtx(etx);
|
||||
m.setBinary(useBinary);
|
||||
@@ -234,9 +240,18 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
if (traceGen != null) {
|
||||
m.setValue(11, traceGen.nextTrace(), IsoType.NUMERIC, 6);
|
||||
}
|
||||
if (setDate) {
|
||||
m.setValue(7, new Date(), IsoType.DATE10, 10);
|
||||
}
|
||||
if (setDate) {
|
||||
if (m.hasField(7)) {
|
||||
//We may have a field with a timezone but no value
|
||||
m.updateValue(7, new Date());
|
||||
} else {
|
||||
IsoValue<Date> now = new IsoValue<>(IsoType.DATE10, new Date());
|
||||
if (DateTimeParseInfo.getDefaultTimeZone() != null) {
|
||||
now.setTimeZone(DateTimeParseInfo.getDefaultTimeZone());
|
||||
}
|
||||
m.setField(7, now);
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
@@ -274,6 +289,9 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
|
||||
/** Sets the timezone for the specified FieldParseInfo, if it's needed for parsing dates. */
|
||||
public void setTimezoneForParseGuide(int messageType, int field, TimeZone tz) {
|
||||
if (field == 0) {
|
||||
DateTimeParseInfo.setDefaultTimeZone(tz);
|
||||
}
|
||||
Map<Integer, FieldParseInfo> guide = parseMap.get(messageType);
|
||||
if (guide != null) {
|
||||
FieldParseInfo fpi = guide.get(field);
|
||||
@@ -286,20 +304,33 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
field, messageType);
|
||||
}
|
||||
|
||||
/** Convenience for parseMessage(buf, isoHeaderLength, false) */
|
||||
public T parseMessage(byte[] buf, int isoHeaderLength)
|
||||
throws ParseException, UnsupportedEncodingException {
|
||||
return parseMessage(buf, isoHeaderLength, false);
|
||||
}
|
||||
|
||||
/** Creates a new message instance from the buffer, which must contain a valid ISO8583
|
||||
* message. If the factory is set to use binary messages then it will try to parse
|
||||
* a binary message.
|
||||
* @param buf The byte buffer containing the message. Must not include the length header.
|
||||
* @param isoHeaderLength The expected length of the ISO header, after which the message type
|
||||
* and the rest of the message must come. */
|
||||
public T parseMessage(byte[] buf, int isoHeaderLength)
|
||||
throws ParseException, UnsupportedEncodingException {
|
||||
public T parseMessage(byte[] buf, int isoHeaderLength, boolean binaryIsoHeader)
|
||||
throws ParseException, UnsupportedEncodingException {
|
||||
final int minlength = isoHeaderLength+(useBinary?2:4)+(binBitmap||useBinary ? 8:16);
|
||||
if (buf.length < minlength) {
|
||||
throw new ParseException("Insufficient buffer length, needs to be at least " + minlength, 0);
|
||||
}
|
||||
final T m = createIsoMessage(isoHeaderLength > 0 ?
|
||||
new String(buf, 0, isoHeaderLength, encoding) : null);
|
||||
final T m;
|
||||
if (binaryIsoHeader && isoHeaderLength > 0) {
|
||||
byte[] _bih = new byte[isoHeaderLength];
|
||||
System.arraycopy(buf, 0, _bih, 0, isoHeaderLength);
|
||||
m = createIsoMessageWithBinaryHeader(_bih);
|
||||
} else {
|
||||
m = createIsoMessage(isoHeaderLength > 0 ?
|
||||
new String(buf, 0, isoHeaderLength, encoding) : null);
|
||||
}
|
||||
m.setCharacterEncoding(encoding);
|
||||
final int type;
|
||||
if (useBinary) {
|
||||
@@ -308,9 +339,9 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
type = Integer.parseInt(new String(buf, isoHeaderLength, 4, encoding), 16);
|
||||
} else {
|
||||
type = ((buf[isoHeaderLength] - 48) << 12)
|
||||
| ((buf[isoHeaderLength + 1] - 48) << 8)
|
||||
| ((buf[isoHeaderLength + 2] - 48) << 4)
|
||||
| (buf[isoHeaderLength + 3] - 48);
|
||||
| ((buf[isoHeaderLength + 1] - 48) << 8)
|
||||
| ((buf[isoHeaderLength + 2] - 48) << 4)
|
||||
| (buf[isoHeaderLength + 3] - 48);
|
||||
}
|
||||
m.setType(type);
|
||||
//Parse the bitmap (primary first)
|
||||
@@ -362,7 +393,7 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
bs.set(pos++, ((bitmapBuffer[i] - 55) & 8) > 0);
|
||||
bs.set(pos++, ((bitmapBuffer[i] - 55) & 4) > 0);
|
||||
bs.set(pos++, ((bitmapBuffer[i] - 55) & 2) > 0);
|
||||
bs.set(pos++, ((bitmapBuffer[i] - 5) & 1) > 0);
|
||||
bs.set(pos++, ((bitmapBuffer[i] - 55) & 1) > 0);
|
||||
} else if (bitmapBuffer[i] >= 'a' && bitmapBuffer[i] <= 'f') {
|
||||
bs.set(pos++, ((bitmapBuffer[i] - 87) & 8) > 0);
|
||||
bs.set(pos++, ((bitmapBuffer[i] - 87) & 4) > 0);
|
||||
@@ -389,7 +420,7 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
bs.set(pos++, ((bitmapBuffer[i] - 55) & 8) > 0);
|
||||
bs.set(pos++, ((bitmapBuffer[i] - 55) & 4) > 0);
|
||||
bs.set(pos++, ((bitmapBuffer[i] - 55) & 2) > 0);
|
||||
bs.set(pos++, ((bitmapBuffer[i] - 5) & 1) > 0);
|
||||
bs.set(pos++, ((bitmapBuffer[i] - 55) & 1) > 0);
|
||||
} else if (bitmapBuffer[i] >= 'a' && bitmapBuffer[i] <= 'f') {
|
||||
bs.set(pos++, ((bitmapBuffer[i] - 87) & 8) > 0);
|
||||
bs.set(pos++, ((bitmapBuffer[i] - 87) & 4) > 0);
|
||||
@@ -413,18 +444,22 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
if (index == null) {
|
||||
log.error(String.format("ISO8583 MessageFactory has no parsing guide for message type %04x [%s]",
|
||||
type, new String(buf)));
|
||||
return null;
|
||||
throw new ParseException(String.format(
|
||||
"ISO8583 MessageFactory has no parsing guide for message type %04x [%s]",
|
||||
type,
|
||||
new String(buf)), 0);
|
||||
}
|
||||
//First we check if the message contains fields not specified in the parsing template
|
||||
boolean abandon = false;
|
||||
for (int i = 1; i < bs.length(); i++) {
|
||||
if (bs.get(i) && !index.contains(i+1)) {
|
||||
log.warn("ISO8583 MessageFactory cannot parse field {}: unspecified in parsing guide", i+1);
|
||||
log.warn("ISO8583 MessageFactory cannot parse field {}: unspecified in parsing guide for type {}",
|
||||
i+1, Integer.toString(type, 16));
|
||||
abandon = true;
|
||||
}
|
||||
}
|
||||
if (abandon) {
|
||||
return m;
|
||||
throw new ParseException("ISO8583 MessageFactory cannot parse fields", 0);
|
||||
}
|
||||
//Now we parse each field
|
||||
if (useBinary) {
|
||||
@@ -444,6 +479,8 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
if (val != null) {
|
||||
if (val.getType() == IsoType.NUMERIC || val.getType() == IsoType.DATE10
|
||||
|| val.getType() == IsoType.DATE4
|
||||
|| val.getType() == IsoType.DATE12
|
||||
|| val.getType() == IsoType.DATE14
|
||||
|| val.getType() == IsoType.DATE_EXP
|
||||
|| val.getType() == IsoType.AMOUNT
|
||||
|| val.getType() == IsoType.TIME) {
|
||||
@@ -505,7 +542,18 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
return (T)new IsoMessage(header);
|
||||
}
|
||||
|
||||
/** Sets whether the factory should set the current date on newly created messages,
|
||||
/** Creates a Iso message with the specified binary ISO header.
|
||||
* Override this method in the subclass to provide your
|
||||
* own implementations of IsoMessage.
|
||||
* @param binHeader The optional ISO header that goes before the message type
|
||||
* @return IsoMessage
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected T createIsoMessageWithBinaryHeader(byte[] binHeader) {
|
||||
return (T)new IsoMessage(binHeader);
|
||||
}
|
||||
|
||||
/** Sets whether the factory should set the current date on newly created messages,
|
||||
* in field 7. Default is false. */
|
||||
public void setAssignDate(boolean flag) {
|
||||
setDate = flag;
|
||||
@@ -542,6 +590,7 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
isoHeaders.remove(type);
|
||||
} else {
|
||||
isoHeaders.put(type, value);
|
||||
binIsoHeaders.remove(type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,6 +599,22 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
return isoHeaders.get(type);
|
||||
}
|
||||
|
||||
/** Sets the ISO header for a specific message type, in binary format.
|
||||
* @param type The message type, for example 0x200.
|
||||
* @param value The ISO header, or NULL to remove any headers for this message type. */
|
||||
public void setBinaryIsoHeader(int type, byte[] value) {
|
||||
if (value == null) {
|
||||
binIsoHeaders.remove(type);
|
||||
} else {
|
||||
binIsoHeaders.put(type, value);
|
||||
isoHeaders.remove(type);
|
||||
}
|
||||
}
|
||||
/** Returns the binary ISO header used for the specified type. */
|
||||
public byte[] getBinaryIsoHeader(int type) {
|
||||
return binIsoHeaders.get(type);
|
||||
}
|
||||
|
||||
/** Adds a message template to the factory. If there was a template for the same
|
||||
* message type as the new one, it is overwritten. */
|
||||
public void addMessageTemplate(T templ) {
|
||||
@@ -576,6 +641,7 @@ public class MessageFactory<T extends IsoMessage> {
|
||||
parseMap = Collections.unmodifiableMap(parseMap);
|
||||
parseOrder = Collections.unmodifiableMap(parseOrder);
|
||||
isoHeaders = Collections.unmodifiableMap(isoHeaders);
|
||||
binIsoHeaders = Collections.unmodifiableMap(binIsoHeaders);
|
||||
customFields = Collections.unmodifiableMap(customFields);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,16 +32,30 @@ import java.math.BigInteger;
|
||||
*/
|
||||
public class BigIntBcdCodec implements CustomBinaryField<BigInteger> {
|
||||
|
||||
private final boolean rightPadded;
|
||||
|
||||
public BigIntBcdCodec() {
|
||||
this(false);
|
||||
}
|
||||
public BigIntBcdCodec(boolean rightPadded) {
|
||||
this.rightPadded = rightPadded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger decodeBinaryField(byte[] value, int pos, int len) {
|
||||
return Bcd.decodeToBigInteger(value, pos, len*2);
|
||||
return rightPadded ? Bcd.decodeRightPaddedToBigInteger(value, pos, len*2)
|
||||
: Bcd.decodeToBigInteger(value, pos, len*2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encodeBinaryField(BigInteger value) {
|
||||
final String s = value.toString(10);
|
||||
final byte[] buf = new byte[s.length() / 2 + s.length() % 2];
|
||||
Bcd.encode(s, buf);
|
||||
if (rightPadded) {
|
||||
Bcd.encodeRightPadded(s, buf);
|
||||
} else {
|
||||
Bcd.encode(s, buf);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ public class CompositeField implements CustomBinaryField<CompositeField> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> IsoValue<T> getField(int idx) {
|
||||
if (idx < 0 || idx > values.size())return null;
|
||||
if (idx < 0 || idx >= values.size())return null;
|
||||
return values.get(idx);
|
||||
}
|
||||
public <T> T getObjectValue(int idx) {
|
||||
@@ -79,14 +79,15 @@ public class CompositeField implements CustomBinaryField<CompositeField> {
|
||||
public CompositeField decodeBinaryField(byte[] buf, int offset, int length) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
List<IsoValue> vals = new ArrayList<>(parsers.size());
|
||||
int pos = 0;
|
||||
int pos = offset;
|
||||
try {
|
||||
for (FieldParseInfo fpi : parsers) {
|
||||
IsoValue<?> v = fpi.parseBinary(0, buf, pos, null);
|
||||
IsoValue<?> v = fpi.parseBinary(0, buf, pos, fpi.getDecoder());
|
||||
if (v != null) {
|
||||
if (v.getType() == IsoType.NUMERIC || v.getType() == IsoType.DATE10
|
||||
|| v.getType() == IsoType.DATE4 || v.getType() == IsoType.DATE_EXP
|
||||
|| v.getType() == IsoType.AMOUNT || v.getType() == IsoType.TIME) {
|
||||
|| v.getType() == IsoType.AMOUNT || v.getType() == IsoType.TIME
|
||||
|| v.getType() == IsoType.DATE12 || v.getType() == IsoType.DATE14) {
|
||||
pos += (v.getLength() / 2) + (v.getLength() % 2);
|
||||
} else {
|
||||
pos += v.getLength();
|
||||
@@ -120,7 +121,7 @@ public class CompositeField implements CustomBinaryField<CompositeField> {
|
||||
int pos = 0;
|
||||
try {
|
||||
for (FieldParseInfo fpi : parsers) {
|
||||
IsoValue<?> v = fpi.parse(0, buf, pos, null);
|
||||
IsoValue<?> v = fpi.parse(0, buf, pos, fpi.getDecoder());
|
||||
if (v != null) {
|
||||
pos += v.toString().getBytes(fpi.getCharacterEncoding()).length;
|
||||
if (v.getType() == IsoType.LLVAR || v.getType() == IsoType.LLBIN) {
|
||||
|
||||
@@ -30,16 +30,30 @@ import com.solab.iso8583.util.Bcd;
|
||||
*/
|
||||
public class LongBcdCodec implements CustomBinaryField<Long> {
|
||||
|
||||
private final boolean rightPadded;
|
||||
|
||||
public LongBcdCodec() {
|
||||
this(false);
|
||||
}
|
||||
public LongBcdCodec(boolean rightPadding) {
|
||||
rightPadded = rightPadding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long decodeBinaryField(byte[] value, int pos, int length) {
|
||||
return Bcd.decodeToLong(value, pos, length*2);
|
||||
return rightPadded ? Bcd.decodeRightPaddedToLong(value, pos, length*2)
|
||||
: Bcd.decodeToLong(value, pos, length*2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encodeBinaryField(Long value) {
|
||||
final String s = Long.toString(value, 10);
|
||||
final byte[] buf = new byte[s.length() / 2 + s.length() % 2];
|
||||
Bcd.encode(s, buf);
|
||||
if (rightPadded) {
|
||||
Bcd.encodeRightPadded(s, buf);
|
||||
} else {
|
||||
Bcd.encode(s, buf);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,29 +20,31 @@ package com.solab.iso8583.parse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.net.URL;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import com.solab.iso8583.*;
|
||||
import com.solab.iso8583.codecs.CompositeField;
|
||||
import com.solab.iso8583.util.HexCodec;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.EntityResolver;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import com.solab.iso8583.CustomField;
|
||||
import com.solab.iso8583.IsoMessage;
|
||||
import com.solab.iso8583.IsoType;
|
||||
import com.solab.iso8583.MessageFactory;
|
||||
|
||||
/** This class is used to parse a XML configuration file and configure
|
||||
* a MessageFactory with the values from it.
|
||||
*
|
||||
@@ -85,7 +87,7 @@ public class ConfigParser {
|
||||
try (InputStream ins = loader.getResourceAsStream(path)) {
|
||||
if (ins != null) {
|
||||
log.debug("ISO8583 Parsing config from classpath file {}", path);
|
||||
parse(mfact, ins);
|
||||
parse(mfact, new InputSource(ins));
|
||||
} else {
|
||||
log.error("ISO8583 File not found in classpath: {}", path);
|
||||
}
|
||||
@@ -97,11 +99,18 @@ public class ConfigParser {
|
||||
public static MessageFactory<IsoMessage> createFromUrl(URL url) throws IOException {
|
||||
MessageFactory<IsoMessage> mfact = new MessageFactory<>();
|
||||
try (InputStream stream = url.openStream()) {
|
||||
parse(mfact, stream);
|
||||
parse(mfact, new InputSource(stream));
|
||||
}
|
||||
return mfact;
|
||||
}
|
||||
|
||||
/** Creates a messageFactory from the XML contained in the specified Reader. */
|
||||
public static MessageFactory<IsoMessage> createFromReader(Reader reader) throws IOException {
|
||||
MessageFactory<IsoMessage> mfact = new MessageFactory<>();
|
||||
parse(mfact, new InputSource(reader));
|
||||
return mfact;
|
||||
}
|
||||
|
||||
protected static <T extends IsoMessage> void parseHeaders(
|
||||
final NodeList nodes, final MessageFactory<T> mfact) throws IOException {
|
||||
ArrayList<Element> refs = null;
|
||||
@@ -121,10 +130,16 @@ public class ConfigParser {
|
||||
else throw new IOException("Invalid ISO8583 header element");
|
||||
} else {
|
||||
String header = elem.getChildNodes().item(0).getNodeValue();
|
||||
boolean binHeader = "true".equals(elem.getAttribute("binary"));
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Adding ISO8583 header for type {}: {}", elem.getAttribute("type"), header);
|
||||
log.trace("Adding {}ISO8583 header for type {}: {}",
|
||||
binHeader?"binary ":"", elem.getAttribute("type"), header);
|
||||
}
|
||||
if (binHeader) {
|
||||
mfact.setBinaryIsoHeader(type, HexCodec.hexDecode(header));
|
||||
} else {
|
||||
mfact.setIsoHeader(type, header);
|
||||
}
|
||||
mfact.setIsoHeader(type, header);
|
||||
}
|
||||
}
|
||||
if (refs != null) {
|
||||
@@ -178,15 +193,14 @@ public class ConfigParser {
|
||||
NodeList fields = elem.getElementsByTagName("field");
|
||||
for (int j = 0; j < fields.getLength(); j++) {
|
||||
Element f = (Element)fields.item(j);
|
||||
int num = Integer.parseInt(f.getAttribute("num"));
|
||||
IsoType itype = IsoType.valueOf(f.getAttribute("type"));
|
||||
int length = 0;
|
||||
if (f.getAttribute("length").length() > 0) {
|
||||
length = Integer.parseInt(f.getAttribute("length"));
|
||||
if (f.getParentNode()==elem) {
|
||||
final int num = Integer.parseInt(f.getAttribute("num"));
|
||||
IsoValue<?> v = getTemplateField(f, mfact, true);
|
||||
if (v != null) {
|
||||
v.setCharacterEncoding(mfact.getCharacterEncoding());
|
||||
}
|
||||
m.setField(num, v);
|
||||
}
|
||||
String v = f.getChildNodes().item(0).getNodeValue();
|
||||
CustomField<Object> _cf = mfact.getCustomField(num);
|
||||
m.setValue(num, _cf == null ? v : _cf.decodeField(v), _cf, itype, length);
|
||||
}
|
||||
mfact.addMessageTemplate(m);
|
||||
}
|
||||
@@ -199,7 +213,7 @@ public class ConfigParser {
|
||||
+ elem.getAttribute("type") + " extends invalid template "
|
||||
+ elem.getAttribute("extends"));
|
||||
}
|
||||
T tref = mfact.getMessageTemplate(ref);
|
||||
IsoMessage tref = mfact.getMessageTemplate(ref);
|
||||
if (tref == null) {
|
||||
throw new IllegalArgumentException("Message template "
|
||||
+ elem.getAttribute("type") + " extends nonexistent template "
|
||||
@@ -218,18 +232,12 @@ public class ConfigParser {
|
||||
for (int j = 0; j < fields.getLength(); j++) {
|
||||
Element f = (Element)fields.item(j);
|
||||
int num = Integer.parseInt(f.getAttribute("num"));
|
||||
final String typedef = f.getAttribute("type");
|
||||
if ("exclude".equals(typedef)) {
|
||||
m.setField(num, null);
|
||||
} else {
|
||||
IsoType itype = IsoType.valueOf(typedef);
|
||||
int length = 0;
|
||||
if (f.getAttribute("length").length() > 0) {
|
||||
length = Integer.parseInt(f.getAttribute("length"));
|
||||
if (f.getParentNode()==elem) {
|
||||
IsoValue<?> v = getTemplateField(f, mfact, true);
|
||||
if (v != null) {
|
||||
v.setCharacterEncoding(mfact.getCharacterEncoding());
|
||||
}
|
||||
String v = f.getChildNodes().item(0).getNodeValue();
|
||||
CustomField<Object> _cf = mfact.getCustomField(num);
|
||||
m.setValue(num, _cf == null ? v : _cf.decodeField(v), _cf, itype, length);
|
||||
m.setField(num, v);
|
||||
}
|
||||
}
|
||||
mfact.addMessageTemplate(m);
|
||||
@@ -237,8 +245,67 @@ public class ConfigParser {
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates an IsoValue from the XML definition in a message template.
|
||||
* If it's for a toplevel field and the message factory has a codec for this field,
|
||||
* that codec is assigned to that field. For nested fields, a CompositeField is
|
||||
* created and populated. */
|
||||
protected static <M extends IsoMessage> IsoValue<?> getTemplateField(
|
||||
Element f, MessageFactory<M> mfact, boolean toplevel) {
|
||||
final int num = Integer.parseInt(f.getAttribute("num"));
|
||||
final String typedef = f.getAttribute("type");
|
||||
if ("exclude".equals(typedef)) {
|
||||
return null;
|
||||
}
|
||||
int length = 0;
|
||||
if (f.getAttribute("length").length() > 0) {
|
||||
length = Integer.parseInt(f.getAttribute("length"));
|
||||
}
|
||||
final IsoType itype = IsoType.valueOf(typedef);
|
||||
final NodeList subs = f.getElementsByTagName("field");
|
||||
if (subs != null && subs.getLength() > 0) {
|
||||
//Composite field
|
||||
final CompositeField cf = new CompositeField();
|
||||
for (int j = 0; j < subs.getLength(); j++) {
|
||||
Element sub = (Element)subs.item(j);
|
||||
if (sub.getParentNode()==f) {
|
||||
IsoValue<?> sv = getTemplateField(sub, mfact, false);
|
||||
if (sv != null) {
|
||||
sv.setCharacterEncoding(mfact.getCharacterEncoding());
|
||||
cf.addValue(sv);
|
||||
}
|
||||
}
|
||||
}
|
||||
IsoValue<?> rv = itype.needsLength() ? new IsoValue<>(itype, cf, length, cf) :
|
||||
new IsoValue<>(itype, cf, cf);
|
||||
if (f.hasAttribute("tz")) {
|
||||
TimeZone tz = TimeZone.getTimeZone(f.getAttribute("tz"));
|
||||
rv.setTimeZone(tz);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
final String v;
|
||||
if (f.getChildNodes().getLength() == 0) {
|
||||
v = "";
|
||||
} else {
|
||||
v = f.getChildNodes().item(0).getNodeValue();
|
||||
}
|
||||
final CustomField<Object> cf = toplevel ? mfact.getCustomField(num) : null;
|
||||
IsoValue<?> rv;
|
||||
if (cf == null) {
|
||||
rv = itype.needsLength() ? new IsoValue<>(itype, v, length) : new IsoValue<>(itype, v);
|
||||
} else {
|
||||
rv = itype.needsLength() ? new IsoValue<>(itype, cf.decodeField(v), length, cf) :
|
||||
new IsoValue<>(itype, cf.decodeField(v), cf);
|
||||
}
|
||||
if (f.hasAttribute("tz")) {
|
||||
TimeZone tz = TimeZone.getTimeZone(f.getAttribute("tz"));
|
||||
rv.setTimeZone(tz);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
protected static <T extends IsoMessage> FieldParseInfo getParser(
|
||||
Element f, MessageFactory<T> mfact) throws IOException {
|
||||
Element f, MessageFactory<T> mfact) {
|
||||
IsoType itype = IsoType.valueOf(f.getAttribute("type"));
|
||||
int length = 0;
|
||||
if (f.getAttribute("length").length() > 0) {
|
||||
@@ -256,6 +323,10 @@ public class ConfigParser {
|
||||
}
|
||||
fpi.setDecoder(combo);
|
||||
}
|
||||
if (f.hasAttribute("tz") && fpi instanceof DateTimeParseInfo) {
|
||||
TimeZone tz = TimeZone.getTimeZone(f.getAttribute("tz"));
|
||||
((DateTimeParseInfo)fpi).setTimeZone(tz);
|
||||
}
|
||||
return fpi;
|
||||
}
|
||||
|
||||
@@ -305,9 +376,8 @@ public class ConfigParser {
|
||||
}
|
||||
HashMap<Integer,FieldParseInfo> child = new HashMap<>();
|
||||
child.putAll(parent);
|
||||
NodeList fields = elem.getElementsByTagName("field");
|
||||
for (int j = 0; j < fields.getLength(); j++) {
|
||||
Element f = (Element)fields.item(j);
|
||||
List<Element> fields = getDirectChildrenByTagName(elem, "field");
|
||||
for (Element f : fields) {
|
||||
int num = Integer.parseInt(f.getAttribute("num"));
|
||||
String typedef = f.getAttribute("type");
|
||||
if ("exclude".equals(typedef)) {
|
||||
@@ -321,12 +391,26 @@ public class ConfigParser {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Element> getDirectChildrenByTagName(Element elem, String tagName) {
|
||||
List<Element> childElementsByTagName = new ArrayList<Element>();
|
||||
NodeList childNodes = elem.getChildNodes();
|
||||
for (int i = 0; i < childNodes.getLength(); i++) {
|
||||
if (childNodes.item(i).getNodeType() == Node.ELEMENT_NODE) {
|
||||
Element childElem = (Element)childNodes.item(i);
|
||||
if (childElem.getTagName().equals(tagName)) {
|
||||
childElementsByTagName.add(childElem);
|
||||
}
|
||||
}
|
||||
}
|
||||
return childElementsByTagName;
|
||||
}
|
||||
|
||||
/** Reads the XML from the stream and configures the message factory with its values.
|
||||
* @param mfact The message factory to be configured with the values read from the XML.
|
||||
* @param stream The InputStream containing the XML configuration. */
|
||||
* @param source The InputSource containing the XML configuration. */
|
||||
protected static <T extends IsoMessage> void parse(
|
||||
MessageFactory<T> mfact, InputStream stream) throws IOException {
|
||||
MessageFactory<T> mfact, InputSource source) throws IOException {
|
||||
final DocumentBuilderFactory docfact = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder docb = null;
|
||||
Document doc = null;
|
||||
@@ -347,7 +431,7 @@ public class ConfigParser {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
doc = docb.parse(stream);
|
||||
doc = docb.parse(source);
|
||||
} catch (ParserConfigurationException | SAXException ex) {
|
||||
log.error("ISO8583 Cannot parse XML configuration", ex);
|
||||
return;
|
||||
@@ -376,7 +460,7 @@ public class ConfigParser {
|
||||
public static <T extends IsoMessage> void configureFromUrl(
|
||||
MessageFactory<T> mfact, URL url) throws IOException {
|
||||
try (InputStream stream = url.openStream()) {
|
||||
parse(mfact, stream);
|
||||
parse(mfact, new InputSource(stream));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,13 +472,19 @@ public class ConfigParser {
|
||||
try (InputStream ins = mfact.getClass().getClassLoader().getResourceAsStream(path)) {
|
||||
if (ins != null) {
|
||||
log.debug("ISO8583 Parsing config from classpath file {}", path);
|
||||
parse(mfact, ins);
|
||||
parse(mfact, new InputSource(ins));
|
||||
} else {
|
||||
log.warn("ISO8583 File not found in classpath: {}", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Configures a MessageFactory using the XML data obtained from the specified Reader. */
|
||||
public static <T extends IsoMessage> void configureFromReader(MessageFactory<T> mfact, Reader reader)
|
||||
throws IOException {
|
||||
parse(mfact, new InputSource(reader));
|
||||
}
|
||||
|
||||
/** Parses a message type expressed as a hex string and returns the integer number.
|
||||
* For example, "0200" or "200" return the number 512 (0x200) */
|
||||
private static int parseType(String type) throws IOException {
|
||||
|
||||
@@ -67,11 +67,7 @@ public class Date10ParseInfo extends DateTimeParseInfo {
|
||||
cal.set(Calendar.SECOND, ((buf[pos + 8] - 48) * 10) + buf[pos + 9] - 48);
|
||||
}
|
||||
cal.set(Calendar.MILLISECOND,0);
|
||||
if (tz != null) {
|
||||
cal.setTimeZone(tz);
|
||||
}
|
||||
adjustWithFutureTolerance(cal);
|
||||
return new IsoValue<>(type, cal.getTime(), null);
|
||||
return createValue(cal, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,11 +97,7 @@ public class Date10ParseInfo extends DateTimeParseInfo {
|
||||
cal.set(Calendar.MINUTE, tens[3]);
|
||||
cal.set(Calendar.SECOND, tens[4]);
|
||||
cal.set(Calendar.MILLISECOND,0);
|
||||
if (tz != null) {
|
||||
cal.setTimeZone(tz);
|
||||
}
|
||||
adjustWithFutureTolerance(cal);
|
||||
return new IsoValue<>(type, cal.getTime(), null);
|
||||
return createValue(cal, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.solab.iso8583.parse;
|
||||
|
||||
import com.solab.iso8583.CustomField;
|
||||
import com.solab.iso8583.IsoType;
|
||||
import com.solab.iso8583.IsoValue;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Blabla.
|
||||
*
|
||||
* @author Enrique Zamudio
|
||||
* Date: 06/01/16 10:52 AM
|
||||
*/
|
||||
public class Date12ParseInfo extends DateTimeParseInfo {
|
||||
|
||||
public Date12ParseInfo() {
|
||||
super(IsoType.DATE12, 12);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> IsoValue<Date> parse(final int field, final byte[] buf,
|
||||
final int pos, final CustomField<T> custom)
|
||||
throws ParseException, UnsupportedEncodingException {
|
||||
if (pos < 0) {
|
||||
throw new ParseException(String.format("Invalid DATE12 field %d position %d",
|
||||
field, pos), pos);
|
||||
}
|
||||
if (pos+12 > buf.length) {
|
||||
throw new ParseException(String.format("Insufficient data for DATE12 field %d, pos %d",
|
||||
field, pos), pos);
|
||||
}
|
||||
//A SimpleDateFormat in the case of dates won't help because of the missing data
|
||||
//we have to use the current date for reference and change what comes in the buffer
|
||||
Calendar cal = Calendar.getInstance();
|
||||
//Set the month in the date
|
||||
int year;
|
||||
if (forceStringDecoding) {
|
||||
year = Integer.parseInt(new String(buf, pos, 2, getCharacterEncoding()), 10);
|
||||
cal.set(Calendar.MONTH, Integer.parseInt(new String(buf, pos, 2, getCharacterEncoding()), 10)-1);
|
||||
cal.set(Calendar.DATE, Integer.parseInt(new String(buf, pos+2, 2, getCharacterEncoding()), 10));
|
||||
cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(new String(buf, pos+4, 2, getCharacterEncoding()), 10));
|
||||
cal.set(Calendar.MINUTE, Integer.parseInt(new String(buf, pos+6, 2, getCharacterEncoding()), 10));
|
||||
cal.set(Calendar.SECOND, Integer.parseInt(new String(buf, pos+8, 2, getCharacterEncoding()), 10));
|
||||
} else {
|
||||
year = ((buf[pos] - 48) * 10) + buf[pos + 1] - 48;
|
||||
cal.set(Calendar.MONTH, ((buf[pos+2] - 48) * 10) + buf[pos + 3] - 49);
|
||||
cal.set(Calendar.DATE, ((buf[pos + 4] - 48) * 10) + buf[pos + 5] - 48);
|
||||
cal.set(Calendar.HOUR_OF_DAY, ((buf[pos + 6] - 48) * 10) + buf[pos + 7] - 48);
|
||||
cal.set(Calendar.MINUTE, ((buf[pos + 8] - 48) * 10) + buf[pos + 9] - 48);
|
||||
cal.set(Calendar.SECOND, ((buf[pos + 10] - 48) * 10) + buf[pos + 11] - 48);
|
||||
}
|
||||
if (year > 50) {
|
||||
cal.set(Calendar.YEAR, 1900+year);
|
||||
} else {
|
||||
cal.set(Calendar.YEAR, 2000+year);
|
||||
}
|
||||
cal.set(Calendar.MILLISECOND,0);
|
||||
return createValue(cal, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> IsoValue<Date> parseBinary(final int field, final byte[] buf,
|
||||
final int pos, final CustomField<T> custom)
|
||||
throws ParseException {
|
||||
if (pos < 0) {
|
||||
throw new ParseException(String.format("Invalid DATE12 field %d position %d",
|
||||
field, pos), pos);
|
||||
}
|
||||
if (pos+6 > buf.length) {
|
||||
throw new ParseException(String.format("Insufficient data for DATE12 field %d, pos %d",
|
||||
field, pos), pos);
|
||||
}
|
||||
int[] tens = new int[6];
|
||||
int start = 0;
|
||||
for (int i = pos; i < pos + tens.length; i++) {
|
||||
tens[start++] = (((buf[i] & 0xf0) >> 4) * 10) + (buf[i] & 0x0f);
|
||||
}
|
||||
Calendar cal = Calendar.getInstance();
|
||||
//A SimpleDateFormat in the case of dates won't help because of the missing data
|
||||
//we have to use the current date for reference and change what comes in the buffer
|
||||
//Set the month in the date
|
||||
if (tens[0] > 50) {
|
||||
cal.set(Calendar.YEAR, 1900+tens[0]);
|
||||
} else {
|
||||
cal.set(Calendar.YEAR, 2000+tens[0]);
|
||||
}
|
||||
cal.set(Calendar.MONTH, tens[1] - 1);
|
||||
cal.set(Calendar.DATE, tens[2]);
|
||||
cal.set(Calendar.HOUR_OF_DAY, tens[3]);
|
||||
cal.set(Calendar.MINUTE, tens[4]);
|
||||
cal.set(Calendar.SECOND, tens[5]);
|
||||
cal.set(Calendar.MILLISECOND,0);
|
||||
return createValue(cal, true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.solab.iso8583.parse;
|
||||
|
||||
import com.solab.iso8583.CustomField;
|
||||
import com.solab.iso8583.IsoType;
|
||||
import com.solab.iso8583.IsoValue;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Blabla.
|
||||
*
|
||||
* @author Enrique Zamudio
|
||||
* Date: 06/01/16 10:52 AM
|
||||
*/
|
||||
public class Date14ParseInfo extends DateTimeParseInfo {
|
||||
|
||||
public Date14ParseInfo() {
|
||||
super(IsoType.DATE14, 14);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> IsoValue<Date> parse(final int field, final byte[] buf,
|
||||
final int pos, final CustomField<T> custom)
|
||||
throws ParseException, UnsupportedEncodingException {
|
||||
if (pos < 0) {
|
||||
throw new ParseException(String.format("Invalid DATE14 field %d position %d",
|
||||
field, pos), pos);
|
||||
}
|
||||
if (pos+14 > buf.length) {
|
||||
throw new ParseException(String.format("Insufficient data for DATE14 field %d, pos %d",
|
||||
field, pos), pos);
|
||||
}
|
||||
//A SimpleDateFormat in the case of dates won't help because of the missing data
|
||||
//we have to use the current date for reference and change what comes in the buffer
|
||||
Calendar cal = Calendar.getInstance();
|
||||
//Set the month in the date
|
||||
if (forceStringDecoding) {
|
||||
cal.set(Calendar.YEAR, Integer.parseInt(new String(buf, pos, 4, getCharacterEncoding()), 10));
|
||||
cal.set(Calendar.MONTH, Integer.parseInt(new String(buf, pos, 2, getCharacterEncoding()), 10)-1);
|
||||
cal.set(Calendar.DATE, Integer.parseInt(new String(buf, pos+2, 2, getCharacterEncoding()), 10));
|
||||
cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(new String(buf, pos+4, 2, getCharacterEncoding()), 10));
|
||||
cal.set(Calendar.MINUTE, Integer.parseInt(new String(buf, pos+6, 2, getCharacterEncoding()), 10));
|
||||
cal.set(Calendar.SECOND, Integer.parseInt(new String(buf, pos+8, 2, getCharacterEncoding()), 10));
|
||||
} else {
|
||||
cal.set(Calendar.YEAR, ((buf[pos] - 48) * 1000) + ((buf[pos + 1] - 48) * 100) + ((buf[pos + 2] - 48) * 10) + buf[pos + 3] - 48);
|
||||
cal.set(Calendar.MONTH, ((buf[pos+4] - 48) * 10) + buf[pos + 5] - 49);
|
||||
cal.set(Calendar.DATE, ((buf[pos + 6] - 48) * 10) + buf[pos + 7] - 48);
|
||||
cal.set(Calendar.HOUR_OF_DAY, ((buf[pos + 8] - 48) * 10) + buf[pos + 9] - 48);
|
||||
cal.set(Calendar.MINUTE, ((buf[pos + 10] - 48) * 10) + buf[pos + 11] - 48);
|
||||
cal.set(Calendar.SECOND, ((buf[pos + 12] - 48) * 10) + buf[pos + 13] - 48);
|
||||
}
|
||||
cal.set(Calendar.MILLISECOND,0);
|
||||
return createValue(cal, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> IsoValue<Date> parseBinary(final int field, final byte[] buf,
|
||||
final int pos, final CustomField<T> custom)
|
||||
throws ParseException {
|
||||
if (pos < 0) {
|
||||
throw new ParseException(String.format("Invalid DATE14 field %d position %d",
|
||||
field, pos), pos);
|
||||
}
|
||||
if (pos+7 > buf.length) {
|
||||
throw new ParseException(String.format("Insufficient data for DATE14 field %d, pos %d",
|
||||
field, pos), pos);
|
||||
}
|
||||
int[] tens = new int[7];
|
||||
int start = 0;
|
||||
for (int i = pos; i < pos + tens.length; i++) {
|
||||
tens[start++] = (((buf[i] & 0xf0) >> 4) * 10) + (buf[i] & 0x0f);
|
||||
}
|
||||
Calendar cal = Calendar.getInstance();
|
||||
//A SimpleDateFormat in the case of dates won't help because of the missing data
|
||||
//we have to use the current date for reference and change what comes in the buffer
|
||||
//Set the month in the date
|
||||
cal.set(Calendar.YEAR, (tens[0] * 100) + tens[1]);
|
||||
cal.set(Calendar.MONTH, tens[2] - 1);
|
||||
cal.set(Calendar.DATE, tens[3]);
|
||||
cal.set(Calendar.HOUR_OF_DAY, tens[4]);
|
||||
cal.set(Calendar.MINUTE, tens[5]);
|
||||
cal.set(Calendar.SECOND, tens[6]);
|
||||
cal.set(Calendar.MILLISECOND,0);
|
||||
return createValue(cal, true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -62,11 +62,7 @@ public class Date4ParseInfo extends DateTimeParseInfo {
|
||||
cal.set(Calendar.MONTH, ((buf[pos] - 48) * 10) + buf[pos + 1] - 49);
|
||||
cal.set(Calendar.DATE, ((buf[pos + 2] - 48) * 10) + buf[pos + 3] - 48);
|
||||
}
|
||||
if (tz != null) {
|
||||
cal.setTimeZone(tz);
|
||||
}
|
||||
Date10ParseInfo.adjustWithFutureTolerance(cal);
|
||||
return new IsoValue<>(type, cal.getTime(), null);
|
||||
return createValue(cal, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -90,11 +86,7 @@ public class Date4ParseInfo extends DateTimeParseInfo {
|
||||
cal.set(Calendar.MONTH, tens[0] - 1);
|
||||
cal.set(Calendar.DATE, tens[1]);
|
||||
cal.set(Calendar.MILLISECOND,0);
|
||||
if (tz != null) {
|
||||
cal.setTimeZone(tz);
|
||||
}
|
||||
DateTimeParseInfo.adjustWithFutureTolerance(cal);
|
||||
return new IsoValue<>(type, cal.getTime(), null);
|
||||
return createValue(cal, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.solab.iso8583.parse;
|
||||
|
||||
import com.solab.iso8583.CustomField;
|
||||
import com.solab.iso8583.IsoType;
|
||||
import com.solab.iso8583.IsoValue;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Parser for fields of type DATE6.
|
||||
*
|
||||
* @author Enrique Zamudio
|
||||
* Date: 4/17/18 12:41 PM
|
||||
*/
|
||||
public class Date6ParseInfo extends DateTimeParseInfo {
|
||||
|
||||
public Date6ParseInfo() {
|
||||
super(IsoType.DATE6, 6);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> IsoValue<Date> parse(int field, byte[] buf, int pos, CustomField<T> custom) throws ParseException, UnsupportedEncodingException {
|
||||
if (pos < 0) {
|
||||
throw new ParseException(String.format("Invalid DATE6 field %d position %d",
|
||||
field, pos), pos);
|
||||
}
|
||||
if (pos+6 > buf.length) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for DATE6 field %d, pos %d", field, pos), pos);
|
||||
}
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.set(Calendar.HOUR_OF_DAY, 0);
|
||||
cal.set(Calendar.MINUTE, 0);
|
||||
cal.set(Calendar.SECOND, 0);
|
||||
cal.set(Calendar.MILLISECOND, 0);
|
||||
//Set the month in the date
|
||||
int year;
|
||||
if (forceStringDecoding) {
|
||||
year = Integer.parseInt(new String(buf, pos, 2, getCharacterEncoding()), 10);
|
||||
cal.set(Calendar.MONTH, Integer.parseInt(new String(buf, pos+2, 2, getCharacterEncoding()), 10)-1);
|
||||
cal.set(Calendar.DATE, Integer.parseInt(new String(buf, pos+4, 2, getCharacterEncoding()), 10));
|
||||
} else {
|
||||
year = ((buf[pos] - 48) * 10) + buf[pos + 1] - 48;
|
||||
cal.set(Calendar.MONTH, ((buf[pos + 2] - 48) * 10) + buf[pos + 3] - 49);
|
||||
cal.set(Calendar.DATE, ((buf[pos + 4] - 48) * 10) + buf[pos + 5] - 48);
|
||||
}
|
||||
if (year > 50) {
|
||||
cal.set(Calendar.YEAR, 1900+year);
|
||||
} else {
|
||||
cal.set(Calendar.YEAR, 2000+year);
|
||||
}
|
||||
return createValue(cal, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> IsoValue<Date> parseBinary(int field, byte[] buf, int pos, CustomField<T> custom) throws ParseException, UnsupportedEncodingException {
|
||||
if (pos < 0) {
|
||||
throw new ParseException(String.format("Invalid DATE6 field %d position %d",
|
||||
field, pos), pos);
|
||||
}
|
||||
if (pos+3 > buf.length) {
|
||||
throw new ParseException(String.format("Insufficient data for DATE6 field %d, pos %d",
|
||||
field, pos), pos);
|
||||
}
|
||||
int[] tens = new int[3];
|
||||
int start = 0;
|
||||
for (int i = pos; i < pos + tens.length; i++) {
|
||||
tens[start++] = (((buf[i] & 0xf0) >> 4) * 10) + (buf[i] & 0x0f);
|
||||
}
|
||||
Calendar cal = Calendar.getInstance();
|
||||
//A SimpleDateFormat in the case of dates won't help because of the missing data
|
||||
//we have to use the current date for reference and change what comes in the buffer
|
||||
//Set the month in the date
|
||||
if (tens[0] > 50) {
|
||||
cal.set(Calendar.YEAR, 1900+tens[0]);
|
||||
} else {
|
||||
cal.set(Calendar.YEAR, 2000+tens[0]);
|
||||
}
|
||||
cal.set(Calendar.MONTH, tens[1] - 1);
|
||||
cal.set(Calendar.DATE, tens[2]);
|
||||
cal.set(Calendar.HOUR_OF_DAY, 0);
|
||||
cal.set(Calendar.MINUTE, 0);
|
||||
cal.set(Calendar.SECOND, 0);
|
||||
cal.set(Calendar.MILLISECOND,0);
|
||||
if (tz != null) {
|
||||
cal.setTimeZone(tz);
|
||||
}
|
||||
return createValue(cal, true);
|
||||
}
|
||||
}
|
||||
@@ -64,10 +64,7 @@ public class DateExpParseInfo extends DateTimeParseInfo {
|
||||
+ ((buf[pos] - 48) * 10) + buf[pos + 1] - 48);
|
||||
cal.set(Calendar.MONTH, ((buf[pos + 2] - 48) * 10) + buf[pos + 3] - 49);
|
||||
}
|
||||
if (tz != null) {
|
||||
cal.setTimeZone(tz);
|
||||
}
|
||||
return new IsoValue<>(type, cal.getTime(), null);
|
||||
return createValue(cal, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -96,9 +93,6 @@ public class DateExpParseInfo extends DateTimeParseInfo {
|
||||
cal.set(Calendar.YEAR, cal.get(Calendar.YEAR)
|
||||
- (cal.get(Calendar.YEAR) % 100) + tens[0]);
|
||||
cal.set(Calendar.MONTH, tens[1] - 1);
|
||||
if (tz != null) {
|
||||
cal.setTimeZone(tz);
|
||||
}
|
||||
return new IsoValue<>(type, cal.getTime(), null);
|
||||
return createValue(cal, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.solab.iso8583.parse;
|
||||
|
||||
import com.solab.iso8583.IsoType;
|
||||
import com.solab.iso8583.IsoValue;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
@@ -15,11 +17,19 @@ public abstract class DateTimeParseInfo extends FieldParseInfo {
|
||||
|
||||
protected static final long FUTURE_TOLERANCE;
|
||||
protected TimeZone tz;
|
||||
private static TimeZone defaultTimezone;
|
||||
|
||||
static {
|
||||
FUTURE_TOLERANCE = Long.parseLong(System.getProperty("j8583.future.tolerance", "900000"));
|
||||
}
|
||||
|
||||
public static void setDefaultTimeZone(TimeZone tz) {
|
||||
defaultTimezone = tz;
|
||||
}
|
||||
public static TimeZone getDefaultTimeZone() {
|
||||
return defaultTimezone;
|
||||
}
|
||||
|
||||
protected DateTimeParseInfo(IsoType type, int length) {
|
||||
super(type, length);
|
||||
}
|
||||
@@ -40,4 +50,21 @@ public abstract class DateTimeParseInfo extends FieldParseInfo {
|
||||
}
|
||||
}
|
||||
|
||||
protected IsoValue<Date> createValue(Calendar cal, boolean adjusting) {
|
||||
if (tz != null) {
|
||||
cal.setTimeZone(tz);
|
||||
} else if (getDefaultTimeZone() != null) {
|
||||
cal.setTimeZone(getDefaultTimeZone());
|
||||
}
|
||||
if (adjusting) {
|
||||
adjustWithFutureTolerance(cal);
|
||||
}
|
||||
IsoValue<Date> v = new IsoValue<>(type, cal.getTime(), null);
|
||||
if (tz != null) {
|
||||
v.setTimeZone(tz);
|
||||
} else if (getDefaultTimeZone() != null) {
|
||||
v.setTimeZone(getDefaultTimeZone());
|
||||
}
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,10 @@ public abstract class FieldParseInfo {
|
||||
fpi = new BinaryParseInfo(len);
|
||||
} else if (t == IsoType.DATE10) {
|
||||
fpi = new Date10ParseInfo();
|
||||
} else if (t == IsoType.DATE12) {
|
||||
fpi = new Date12ParseInfo();
|
||||
} else if (t == IsoType.DATE14) {
|
||||
fpi = new Date14ParseInfo();
|
||||
} else if (t == IsoType.DATE4) {
|
||||
fpi = new Date4ParseInfo();
|
||||
} else if (t == IsoType.DATE_EXP) {
|
||||
|
||||
@@ -55,7 +55,8 @@ public class LllbinParseInfo extends FieldParseInfo {
|
||||
l, field, pos), pos);
|
||||
} else if (l+pos+3 > buf.length) {
|
||||
throw new ParseException(String.format(
|
||||
"Insufficient data for LLLBIN field %d, pos %d", field, pos), pos);
|
||||
"Insufficient data for LLLBIN field %d, pos %d len %d",
|
||||
field, pos, l), pos);
|
||||
}
|
||||
byte[] binval = l == 0 ? new byte[0] : HexCodec.hexDecode(new String(buf, pos + 3, l));
|
||||
if (custom == null) {
|
||||
@@ -68,7 +69,8 @@ public class LllbinParseInfo extends FieldParseInfo {
|
||||
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);
|
||||
"Insufficient data for LLLBIN field %d, pos %d len %d",
|
||||
field, pos, l), pos);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
@@ -78,7 +80,8 @@ public class LllbinParseInfo extends FieldParseInfo {
|
||||
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);
|
||||
"Insufficient data for LLLBIN field %d, pos %d len %d",
|
||||
field, pos, l), pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public class LlllvarParseInfo extends FieldParseInfo {
|
||||
"Insufficient data for bin LLLLVAR header, field %d pos %d",
|
||||
field, pos), pos);
|
||||
}
|
||||
final int len = ((buf[pos] & 0xf0) * 1000) + ((buf[pos] & 0x0f) * 100)
|
||||
final int len = (((buf[pos] & 0xf0) >> 4) * 1000) + ((buf[pos] & 0x0f) * 100)
|
||||
+ (((buf[pos + 1] & 0xf0) >> 4) * 10) + (buf[pos + 1] & 0x0f);
|
||||
if (len < 0) {
|
||||
throw new ParseException(String.format(
|
||||
|
||||
@@ -48,18 +48,18 @@ public class LllvarParseInfo extends FieldParseInfo {
|
||||
}
|
||||
final int len = decodeLength(buf, pos, 3);
|
||||
if (len < 0) {
|
||||
throw new ParseException(String.format("Invalid LLLVAR length %d field %d pos %d",
|
||||
len, field, pos), pos);
|
||||
throw new ParseException(String.format("Invalid LLLVAR length %d(%s) field %d pos %d",
|
||||
len, new String(buf, pos, 3), 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);
|
||||
throw new ParseException(String.format("Insufficient data for LLLVAR field %d, pos %d len %d",
|
||||
field, pos, len), pos);
|
||||
}
|
||||
String _v;
|
||||
try {
|
||||
_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);
|
||||
"Insufficient data for LLLVAR header, field %d pos %d len %d", field, pos, len), 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
|
||||
|
||||
@@ -52,14 +52,16 @@ public class LlvarParseInfo extends FieldParseInfo {
|
||||
"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);
|
||||
"Insufficient data for LLVAR field %d, pos %d len %d",
|
||||
field, pos, len), pos);
|
||||
}
|
||||
String _v;
|
||||
try {
|
||||
_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);
|
||||
"Insufficient data for LLVAR header, field %d pos %d len %d",
|
||||
field, pos, len), pos);
|
||||
}
|
||||
//This is new: if the String's length is different from the specified
|
||||
// length in the buffer, there are probably some extended characters.
|
||||
|
||||
@@ -59,10 +59,7 @@ public class TimeParseInfo extends DateTimeParseInfo {
|
||||
cal.set(Calendar.MINUTE, ((buf[pos + 2] - 48) * 10) + buf[pos + 3] - 48);
|
||||
cal.set(Calendar.SECOND, ((buf[pos + 4] - 48) * 10) + buf[pos + 5] - 48);
|
||||
}
|
||||
if (tz != null) {
|
||||
cal.setTimeZone(tz);
|
||||
}
|
||||
return new IsoValue<Date>(type, cal.getTime(), null);
|
||||
return createValue(cal, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -51,6 +51,28 @@ public final class Bcd {
|
||||
return l;
|
||||
}
|
||||
|
||||
public static long decodeRightPaddedToLong(byte[] buf, int pos, int length)
|
||||
throws IndexOutOfBoundsException {
|
||||
if (length > 18) {
|
||||
throw new IndexOutOfBoundsException("Buffer too big to decode as long");
|
||||
}
|
||||
long l = 0;
|
||||
long power = 1L;
|
||||
int end = pos + (length / 2) + (length % 2) - 1;
|
||||
if ((buf[end] & 0xf) == 0xf) {
|
||||
l += (buf[end] & 0xf0) >> 4;
|
||||
power *= 10L;
|
||||
end--;
|
||||
}
|
||||
for (int i = end; i >= pos; i--) {
|
||||
l += (buf[i] & 0x0f) * power;
|
||||
power *= 10L;
|
||||
l += ((buf[i] & 0xf0) >> 4) * power;
|
||||
power *= 10L;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
/** Encode the value as BCD and put it in the buffer. The buffer must be big enough
|
||||
* to store the digits in the original value (half the length of the string). */
|
||||
public static void encode(String value, byte[] buf) {
|
||||
@@ -71,6 +93,30 @@ public final class Bcd {
|
||||
}
|
||||
}
|
||||
|
||||
/** Encode the value as BCD and put it in the buffer. The buffer must be big enough
|
||||
* to store the digits in the original value (half the length of the string).
|
||||
* If the value contains an odd number of digits, the last one is stored in
|
||||
* its own byte at the end, padded with an F nibble. */
|
||||
public static void encodeRightPadded(String value, byte[] buf) {
|
||||
int bufpos = 0;
|
||||
int charpos = 0;
|
||||
int limit = value.length();
|
||||
if (limit % 2 == 1) {
|
||||
limit--;
|
||||
}
|
||||
//encode the rest of the string
|
||||
while (charpos < limit) {
|
||||
buf[bufpos] = (byte)(((value.charAt(charpos) - 48) << 4)
|
||||
| (value.charAt(charpos + 1) - 48));
|
||||
charpos += 2;
|
||||
bufpos++;
|
||||
}
|
||||
if (value.length() % 2 == 1) {
|
||||
buf[bufpos] = (byte)(((value.charAt(limit) - 48) << 4)
|
||||
| 0xf);
|
||||
}
|
||||
}
|
||||
|
||||
/** Decodes a BCD-encoded number as a BigInteger.
|
||||
* @param buf The byte buffer containing the BCD data.
|
||||
* @param pos The starting position in the buffer.
|
||||
@@ -79,11 +125,34 @@ public final class Bcd {
|
||||
throws IndexOutOfBoundsException {
|
||||
char[] digits = new char[length];
|
||||
int start = 0;
|
||||
for (int i = pos; i < pos + (length / 2) + (length % 2); i++) {
|
||||
int i = pos;
|
||||
if (length % 2 != 0) {
|
||||
digits[start++] = (char)((buf[i] & 0x0f) + 48);
|
||||
i++;
|
||||
}
|
||||
for (;i < pos + (length / 2) + (length % 2); i++) {
|
||||
digits[start++] = (char)(((buf[i] & 0xf0) >> 4) + 48);
|
||||
digits[start++] = (char)((buf[i] & 0x0f) + 48);
|
||||
}
|
||||
return new BigInteger(new String(digits));
|
||||
}
|
||||
|
||||
/** Decodes a right-padded BCD-encoded number as a BigInteger.
|
||||
* @param buf The byte buffer containing the BCD data.
|
||||
* @param pos The starting position in the buffer.
|
||||
* @param length The number of DIGITS (not bytes) to read. */
|
||||
public static BigInteger decodeRightPaddedToBigInteger(byte[] buf, int pos, int length)
|
||||
throws IndexOutOfBoundsException {
|
||||
char[] digits = new char[length];
|
||||
int start = 0;
|
||||
int i = pos;
|
||||
int limit = pos + (length / 2) + (length % 2);
|
||||
for (;i < limit; i++) {
|
||||
digits[start++] = (char)(((buf[i] & 0xf0) >> 4) + 48);
|
||||
int r = buf[i] & 0xf;
|
||||
digits[start++] = r == 15 ? ' ' : (char)(r + 48);
|
||||
}
|
||||
return new BigInteger(new String(digits, 0, start).trim());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public class SimpleParser {
|
||||
for (int i = 2; i <= 128; i++) {
|
||||
IsoValue<?> f = m.getField(i);
|
||||
if (f != null) {
|
||||
System.out.printf("%5d %-6s [", i, f.getType());
|
||||
System.out.printf("%5d %-7s (%4d) [", i, f.getType(), f.getLength());
|
||||
System.out.print(f.toString());
|
||||
System.out.println(']');
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<!ELEMENT header ( #PCDATA ) >
|
||||
<!ATTLIST header type NMTOKEN #REQUIRED >
|
||||
<!ATTLIST header ref NMTOKEN #IMPLIED >
|
||||
<!ATTLIST header binary ( true | false ) #IMPLIED >
|
||||
|
||||
<!ELEMENT template ( field* ) >
|
||||
<!ATTLIST template type NMTOKEN #REQUIRED >
|
||||
@@ -15,4 +16,5 @@
|
||||
<!ELEMENT field ( #PCDATA|field )* >
|
||||
<!ATTLIST field num NMTOKEN #REQUIRED >
|
||||
<!ATTLIST field length NMTOKEN #IMPLIED >
|
||||
<!ATTLIST field type ( ALPHA | NUMERIC | AMOUNT | DATE10 | DATE4 | DATE_EXP | TIME | LLVAR | LLLVAR | BINARY | LLBIN | LLLBIN | exclude) #REQUIRED >
|
||||
<!ATTLIST field tz NMTOKEN #IMPLIED >
|
||||
<!ATTLIST field type ( ALPHA | NUMERIC | AMOUNT | DATE4 | DATE10 | DATE12 | DATE14 | DATE_EXP | TIME | LLVAR | LLLVAR | BINARY | LLBIN | LLLBIN | LLLLVAR | LLLLBIN | exclude) #REQUIRED >
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.solab.iso8583;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.text.ParseException;
|
||||
|
||||
import org.junit.Assert;
|
||||
@@ -104,4 +105,23 @@ public class TestBinaries {
|
||||
Assert.assertEquals(sub2, HexCodec.hexEncode(sub1, 0, sub1.length));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test61() throws ParseException, UnsupportedEncodingException {
|
||||
BigInteger bignum = new BigInteger("1234512345123451234");
|
||||
IsoMessage iso1 = mfactBin.newMessage(0x201);
|
||||
iso1.setValue(3, bignum, IsoType.NUMERIC, 19);
|
||||
iso1.setField(7, null);
|
||||
byte[] buf = iso1.writeData();
|
||||
System.out.println(HexCodec.hexEncode(buf, 0, buf.length));
|
||||
IsoMessage iso2 = mfactBin.parseMessage(buf, 0);
|
||||
Assert.assertEquals(bignum, iso2.getObjectValue(3));
|
||||
bignum = new BigInteger("1234512345123451234522");
|
||||
iso1 = mfactBin.newMessage(0x202);
|
||||
iso1.setValue(3, bignum, IsoType.NUMERIC, 22);
|
||||
iso1.setField(7, null);
|
||||
buf = iso1.writeData();
|
||||
System.out.println(HexCodec.hexEncode(buf, 0, buf.length));
|
||||
iso2 = mfactBin.parseMessage(buf, 0);
|
||||
Assert.assertEquals(bignum, iso2.getObjectValue(3));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.solab.iso8583;
|
||||
|
||||
import com.solab.iso8583.codecs.CompositeField;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -14,10 +15,15 @@ import java.text.ParseException;
|
||||
*/
|
||||
public class TestConfigParser {
|
||||
|
||||
private MessageFactory<IsoMessage> config(String path) throws IOException {
|
||||
MessageFactory<IsoMessage> mfact = new MessageFactory<>();
|
||||
mfact.setConfigPath(path);
|
||||
return mfact;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParser() throws IOException, ParseException {
|
||||
MessageFactory<IsoMessage> mfact = new MessageFactory<>();
|
||||
mfact.setConfigPath("config.xml");
|
||||
final MessageFactory<IsoMessage> mfact = config("config.xml");
|
||||
//Headers
|
||||
Assert.assertNotNull(mfact.getIsoHeader(0x800));
|
||||
Assert.assertNotNull(mfact.getIsoHeader(0x810));
|
||||
@@ -27,7 +33,7 @@ public class TestConfigParser {
|
||||
Assert.assertNotNull(m200);
|
||||
final IsoMessage m400 = mfact.getMessageTemplate(0x400);
|
||||
Assert.assertNotNull(m400);
|
||||
for (int i=2; i <89; i++) {
|
||||
for (int i = 2; i < 89; i++) {
|
||||
IsoValue<?> v = m200.getField(i);
|
||||
if (v == null) {
|
||||
Assert.assertFalse(m400.hasField(i));
|
||||
@@ -58,10 +64,109 @@ public class TestConfigParser {
|
||||
Assert.assertTrue(m.hasField(39));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleCompositeParsers() throws IOException, ParseException {
|
||||
MessageFactory<IsoMessage> mfact = config("composites.xml");
|
||||
|
||||
IsoMessage m = mfact.parseMessage("01000040000000000000016one 03two12345.".getBytes(), 0);
|
||||
Assert.assertNotNull(m);
|
||||
CompositeField f = m.getObjectValue(10);
|
||||
Assert.assertNotNull(f);
|
||||
Assert.assertEquals(4, f.getValues().size());
|
||||
Assert.assertEquals("one ", f.getObjectValue(0));
|
||||
Assert.assertEquals("two", f.getObjectValue(1));
|
||||
Assert.assertEquals("12345", f.getObjectValue(2));
|
||||
Assert.assertEquals(".", f.getObjectValue(3));
|
||||
|
||||
m = mfact.parseMessage("01000040000000000000018ALPHA05LLVAR12345X".getBytes(), 0);
|
||||
Assert.assertNotNull(m);
|
||||
Assert.assertTrue(m.hasField(10));
|
||||
f = m.getObjectValue(10);
|
||||
Assert.assertNotNull(f.getField(0));
|
||||
Assert.assertNotNull(f.getField(1));
|
||||
Assert.assertNotNull(f.getField(2));
|
||||
Assert.assertNotNull(f.getField(3));
|
||||
Assert.assertNull(f.getField(4));
|
||||
Assert.assertEquals("ALPHA", f.getObjectValue(0));
|
||||
Assert.assertEquals("LLVAR", f.getObjectValue(1));
|
||||
Assert.assertEquals("12345", f.getObjectValue(2));
|
||||
Assert.assertEquals("X", f.getObjectValue(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedCompositeParser() throws IOException, ParseException {
|
||||
MessageFactory<IsoMessage> mfact = config("composites.xml");
|
||||
IsoMessage m = mfact.parseMessage("01010040000000000000019ALPHA11F1F205F03F4X".getBytes(), 0);
|
||||
Assert.assertNotNull(m);
|
||||
Assert.assertTrue(m.hasField(10));
|
||||
CompositeField f = m.getObjectValue(10);
|
||||
Assert.assertNotNull(f.getField(0));
|
||||
Assert.assertNotNull(f.getField(1));
|
||||
Assert.assertNotNull(f.getField(2));
|
||||
Assert.assertNull(f.getField(3));
|
||||
Assert.assertEquals("ALPHA", f.getObjectValue(0));
|
||||
Assert.assertEquals("X", f.getObjectValue(2));
|
||||
f = f.getObjectValue(1);
|
||||
Assert.assertEquals("F1", f.getObjectValue(0));
|
||||
Assert.assertEquals("F2", f.getObjectValue(1));
|
||||
f = f.getObjectValue(2);
|
||||
Assert.assertEquals("F03", f.getObjectValue(0));
|
||||
Assert.assertEquals("F4", f.getObjectValue(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleCompositeTemplate() throws IOException {
|
||||
MessageFactory<IsoMessage> mfact = config("composites.xml");
|
||||
IsoMessage m = mfact.newMessage(0x100);
|
||||
//Simple composite
|
||||
Assert.assertNotNull(m);
|
||||
Assert.assertFalse(m.hasField(1));
|
||||
Assert.assertFalse(m.hasField(2));
|
||||
Assert.assertFalse(m.hasField(3));
|
||||
Assert.assertFalse(m.hasField(4));
|
||||
CompositeField f = m.getObjectValue(10);
|
||||
Assert.assertNotNull(f);
|
||||
Assert.assertEquals(f.getObjectValue(0), "abcde");
|
||||
Assert.assertEquals(f.getObjectValue(1), "llvar");
|
||||
Assert.assertEquals(f.getObjectValue(2), "12345");
|
||||
Assert.assertEquals(f.getObjectValue(3), "X");
|
||||
Assert.assertFalse(m.hasField(4));
|
||||
}
|
||||
|
||||
private void testNestedCompositeTemplate(int type, int fnum) throws IOException {
|
||||
MessageFactory<IsoMessage> mfact = config("composites.xml");
|
||||
IsoMessage m = mfact.newMessage(type);
|
||||
Assert.assertNotNull(m);
|
||||
Assert.assertFalse(m.hasField(1));
|
||||
Assert.assertFalse(m.hasField(2));
|
||||
Assert.assertFalse(m.hasField(3));
|
||||
Assert.assertFalse(m.hasField(4));
|
||||
CompositeField f = m.getObjectValue(fnum);
|
||||
Assert.assertEquals(f.getObjectValue(0), "fghij");
|
||||
Assert.assertEquals(f.getObjectValue(2), "67890");
|
||||
Assert.assertEquals(f.getObjectValue(3), "Y");
|
||||
f = f.getObjectValue(1);
|
||||
Assert.assertEquals(f.getObjectValue(0), "KL");
|
||||
Assert.assertEquals(f.getObjectValue(1), "mn");
|
||||
f = f.getObjectValue(2);
|
||||
Assert.assertEquals(f.getObjectValue(0), "123");
|
||||
Assert.assertEquals(f.getObjectValue(1), "45");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedCompositeTemplate() throws IOException {
|
||||
testNestedCompositeTemplate(0x101, 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedCompositeFromExtendedTemplate() throws IOException {
|
||||
testNestedCompositeTemplate(0x102, 10);
|
||||
testNestedCompositeTemplate(0x102, 12);
|
||||
}
|
||||
|
||||
@Test //issue 34
|
||||
public void testMultilevelExtendParseGuides() throws IOException, ParseException {
|
||||
MessageFactory<IsoMessage> mfact = new MessageFactory<>();
|
||||
mfact.setConfigPath("issue34.xml");
|
||||
final MessageFactory<IsoMessage> mfact = config("issue34.xml");
|
||||
//Parse a 200
|
||||
final String m200 = "0200422000000880800001X1231235959123456101010202020TERMINAL484";
|
||||
final String m210 = "0210422000000A80800001X123123595912345610101020202099TERMINAL484";
|
||||
@@ -96,4 +201,31 @@ public class TestConfigParser {
|
||||
Assert.assertEquals("99", m.getObjectValue(39));
|
||||
Assert.assertEquals("X", m.getObjectValue(61));
|
||||
}
|
||||
|
||||
@Test // issue 47
|
||||
public void testExtendCompositeWithSameField() throws IOException, ParseException {
|
||||
final MessageFactory<IsoMessage> mfact = config("issue47.xml");
|
||||
|
||||
final String m200 = "02001000000000000004000000100000013ABCDEFGHIJKLM";
|
||||
IsoMessage isoMessage = mfact.parseMessage(m200.getBytes(), 0);
|
||||
|
||||
// check field num 4
|
||||
IsoValue<Object> field4 = isoMessage.getField(4);
|
||||
Assert.assertEquals(IsoType.AMOUNT, field4.getType());
|
||||
Assert.assertEquals(IsoType.AMOUNT.getLength(), field4.getLength());
|
||||
|
||||
// check nested field num 4 from composite field 62
|
||||
CompositeField compositeField62 = isoMessage.<CompositeField>getField(62).getValue();
|
||||
IsoValue<Object> nestedField4 = compositeField62.getField(0); // first in list
|
||||
Assert.assertEquals(IsoType.ALPHA, nestedField4.getType());
|
||||
Assert.assertEquals(13, nestedField4.getLength());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyFields() throws IOException, ParseException {
|
||||
final MessageFactory<IsoMessage> mfact = config("issue64.xml");
|
||||
IsoMessage msg = mfact.newMessage(0x200);
|
||||
Assert.assertEquals("", msg.getObjectValue(3));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,22 +17,28 @@ public class TestFormats {
|
||||
|
||||
@Test
|
||||
public void testDateFormats() {
|
||||
assert IsoType.DATE10.format(date, null).equals("0125213456");
|
||||
assert IsoType.DATE4.format(date, null).equals("0125");
|
||||
assert IsoType.DATE_EXP.format(date, null).equals("7301");
|
||||
assert IsoType.TIME.format(date, null).equals("213456");
|
||||
Assert.assertEquals("0125213456", IsoType.DATE10.format(date, null));
|
||||
Assert.assertEquals("0125", IsoType.DATE4.format(date, null));
|
||||
Assert.assertEquals("7301", IsoType.DATE_EXP.format(date, null));
|
||||
Assert.assertEquals("213456", IsoType.TIME.format(date, null));
|
||||
Assert.assertEquals("730125213456", IsoType.DATE12.format(date, null));
|
||||
Assert.assertEquals("19730125213456", IsoType.DATE14.format(date, null));
|
||||
//Now with GMT
|
||||
TimeZone gmt = TimeZone.getTimeZone("GMT");
|
||||
Assert.assertEquals("0126033456", IsoType.DATE10.format(date, gmt));
|
||||
Assert.assertEquals("0126", IsoType.DATE4.format(date, gmt));
|
||||
Assert.assertEquals("7301", IsoType.DATE_EXP.format(date, gmt));
|
||||
Assert.assertEquals("033456", IsoType.TIME.format(date, gmt));
|
||||
Assert.assertEquals("730126033456", IsoType.DATE12.format(date, gmt));
|
||||
Assert.assertEquals("19730126033456", IsoType.DATE14.format(date, gmt));
|
||||
//And now with GMT+1
|
||||
gmt = TimeZone.getTimeZone("GMT+0100");
|
||||
Assert.assertEquals("0126043456", IsoType.DATE10.format(date, gmt));
|
||||
Assert.assertEquals("0126", IsoType.DATE4.format(date, gmt));
|
||||
Assert.assertEquals("7301", IsoType.DATE_EXP.format(date, gmt));
|
||||
Assert.assertEquals("043456", IsoType.TIME.format(date, gmt));
|
||||
Assert.assertEquals("730126043456", IsoType.DATE12.format(date, gmt));
|
||||
Assert.assertEquals("19730126043456", IsoType.DATE14.format(date, gmt));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.solab.iso8583;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Test ISO headers.
|
||||
*
|
||||
* @author Enrique Zamudio
|
||||
* Date: 01/07/16 8:21 AM
|
||||
*/
|
||||
public class TestHeaders {
|
||||
|
||||
private MessageFactory<IsoMessage> mf;
|
||||
|
||||
@Before
|
||||
public void init() throws IOException {
|
||||
mf = new MessageFactory<>();
|
||||
mf.setCharacterEncoding("UTF-8");
|
||||
mf.setConfigPath("config.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBinaryHeader() throws Exception {
|
||||
IsoMessage m = mf.newMessage(0x280);
|
||||
Assert.assertNotNull(m.getBinaryIsoHeader());
|
||||
byte[] buf = m.writeData();
|
||||
Assert.assertEquals(4+4+16+2, buf.length);
|
||||
for (int i=0; i < 4; i++) {
|
||||
Assert.assertEquals(buf[i], (byte)0xff);
|
||||
}
|
||||
Assert.assertEquals(buf[4], 0x30);
|
||||
Assert.assertEquals(buf[5], 0x32);
|
||||
Assert.assertEquals(buf[6], 0x38);
|
||||
Assert.assertEquals(buf[7], 0x30);
|
||||
//Then parse and check the header is binary 0xffffffff
|
||||
m = mf.parseMessage(buf, 4, true);
|
||||
Assert.assertNull(m.getIsoHeader());
|
||||
buf = m.getBinaryIsoHeader();
|
||||
Assert.assertNotNull(buf);
|
||||
for (int i=0; i < 4; i++) {
|
||||
Assert.assertEquals(buf[i], (byte)0xff);
|
||||
}
|
||||
Assert.assertEquals(0x280, m.getType());
|
||||
Assert.assertTrue(m.hasField(3));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public class TestIsoMessage {
|
||||
|
||||
@Before
|
||||
public void init() throws IOException {
|
||||
mf = new MessageFactory<IsoMessage>();
|
||||
mf = new MessageFactory<>();
|
||||
mf.setCharacterEncoding("UTF-8");
|
||||
mf.setCustomField(48, new CustomField48());
|
||||
mf.setConfigPath("config.xml");
|
||||
@@ -30,31 +30,35 @@ public class TestIsoMessage {
|
||||
@Test
|
||||
public void testCreation() {
|
||||
IsoMessage iso = mf.newMessage(0x200);
|
||||
assert iso.getType() == 0x200;
|
||||
assert iso.getField(3).getType() == IsoType.NUMERIC && "650000".equals(iso.getObjectValue(3));
|
||||
assert iso.getField(32).getType() == IsoType.LLVAR;
|
||||
assert iso.getField(35).getType() == IsoType.LLVAR;
|
||||
assert iso.getField(43).getType() == IsoType.ALPHA && ((String)iso.getObjectValue(43)).length() == 40;
|
||||
assert iso.getField(48).getType() == IsoType.LLLVAR && iso.getObjectValue(48) instanceof CustomField48;
|
||||
assert iso.getField(49).getType() == IsoType.ALPHA;
|
||||
assert iso.getField(60).getType() == IsoType.LLLVAR;
|
||||
assert iso.getField(61).getType() == IsoType.LLLVAR;
|
||||
assert iso.getField(100).getType() == IsoType.LLVAR;
|
||||
assert iso.getField(102).getType() == IsoType.LLVAR;
|
||||
Assert.assertEquals(0x200, iso.getType());
|
||||
Assert.assertTrue(iso.hasEveryField(3, 32, 35, 43, 48, 49, 60, 61, 100, 102));
|
||||
Assert.assertEquals(IsoType.NUMERIC, iso.getField(3).getType());
|
||||
Assert.assertEquals("650000", iso.getObjectValue(3));
|
||||
Assert.assertEquals(IsoType.LLVAR, iso.getField(32).getType());
|
||||
Assert.assertEquals(IsoType.LLVAR, iso.getField(35).getType());
|
||||
Assert.assertEquals(IsoType.ALPHA, iso.getField(43).getType());
|
||||
Assert.assertEquals(40, ((String) iso.getObjectValue(43)).length());
|
||||
Assert.assertEquals(IsoType.LLLVAR, iso.getField(48).getType());
|
||||
Assert.assertTrue(iso.getObjectValue(48) instanceof CustomField48);
|
||||
Assert.assertEquals(IsoType.ALPHA, iso.getField(49).getType());
|
||||
Assert.assertEquals(IsoType.LLLVAR, iso.getField(60).getType());
|
||||
Assert.assertEquals(IsoType.LLLVAR, iso.getField(61).getType());
|
||||
Assert.assertEquals(IsoType.LLVAR, iso.getField(100).getType());
|
||||
Assert.assertEquals(IsoType.LLVAR, iso.getField(102).getType());
|
||||
for (int i = 4; i < 32; i++) {
|
||||
assert !iso.hasField(i);
|
||||
Assert.assertFalse("ISO should not contain " + i, iso.hasField(i));
|
||||
}
|
||||
for (int i = 36; i < 43; i++) {
|
||||
assert !iso.hasField(i);
|
||||
Assert.assertFalse("ISO should not contain " + i, iso.hasField(i));
|
||||
}
|
||||
for (int i = 50; i < 60; i++) {
|
||||
assert !iso.hasField(i);
|
||||
Assert.assertFalse("ISO should not contain " + i, iso.hasField(i));
|
||||
}
|
||||
for (int i = 62; i < 100; i++) {
|
||||
assert !iso.hasField(i);
|
||||
Assert.assertFalse("ISO should not contain " + i, iso.hasField(i));
|
||||
}
|
||||
for (int i = 103; i < 128; i++) {
|
||||
assert !iso.hasField(i);
|
||||
Assert.assertFalse("ISO should not contain " + i, iso.hasField(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,14 +67,15 @@ public class TestIsoMessage {
|
||||
IsoMessage m1 = mf.newMessage(0x200);
|
||||
byte[] buf = m1.writeData();
|
||||
IsoMessage m2 = mf.parseMessage(buf, mf.getIsoHeader(0x200).length());
|
||||
assert m2.getType() == m1.getType();
|
||||
Assert.assertEquals(m2.getType(), m1.getType());
|
||||
for (int i = 2; i < 128; i++) {
|
||||
//Either both have the field or neither have it
|
||||
if (m1.hasField(i) && m2.hasField(i)) {
|
||||
Assert.assertEquals(m1.getField(i).getType(), m2.getField(i).getType());
|
||||
Assert.assertEquals(m1.getObjectValue(i), m2.getObjectValue(i));
|
||||
} else {
|
||||
assert !m1.hasField(i) && !m2.hasField(i);
|
||||
Assert.assertFalse(m1.hasField(i));
|
||||
Assert.assertFalse(m2.hasField(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,16 +113,16 @@ public class TestIsoMessage {
|
||||
public void testTemplating() {
|
||||
IsoMessage iso1 = mf.newMessage(0x200);
|
||||
IsoMessage iso2 = mf.newMessage(0x200);
|
||||
assert iso1 != iso2;
|
||||
assert iso1.getObjectValue(3).equals(iso2.getObjectValue(3));
|
||||
assert iso1.getField(3) != iso2.getField(3);
|
||||
assert iso1.getField(48) != iso2.getField(48);
|
||||
Assert.assertNotSame(iso1, iso2);
|
||||
Assert.assertEquals(iso1.getObjectValue(3), iso2.getObjectValue(3));
|
||||
Assert.assertNotSame(iso1.getField(3), iso2.getField(3));
|
||||
Assert.assertNotSame(iso1.getField(48), iso2.getField(48));
|
||||
CustomField48 cf48_1 = iso1.getObjectValue(48);
|
||||
int origv = cf48_1.getValue2();
|
||||
cf48_1.setValue2(origv + 1000);
|
||||
CustomField48 cf48_2 = iso2.getObjectValue(48);
|
||||
assert cf48_1 == cf48_2;
|
||||
assert cf48_2.getValue2() == origv + 1000;
|
||||
Assert.assertSame(cf48_1, cf48_2);
|
||||
Assert.assertEquals(cf48_2.getValue2(), origv + 1000);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@@ -125,12 +130,12 @@ public class TestIsoMessage {
|
||||
IsoMessage iso = mf.newMessage(0x200);
|
||||
IsoValue<String> f3 = iso.getField(3);
|
||||
iso.updateValue(3, "999999");
|
||||
assert iso.getObjectValue(3).equals("999999");
|
||||
Assert.assertEquals("999999", iso.getObjectValue(3));
|
||||
IsoValue<String> nf3 = iso.getField(3);
|
||||
assert f3 != nf3;
|
||||
assert f3.getType() == nf3.getType();
|
||||
assert f3.getLength() == nf3.getLength();
|
||||
assert f3.getEncoder() == nf3.getEncoder();
|
||||
Assert.assertNotSame(f3, nf3);
|
||||
Assert.assertEquals(f3.getType(), nf3.getType());
|
||||
Assert.assertEquals(f3.getLength(), nf3.getLength());
|
||||
Assert.assertSame(f3.getEncoder(), nf3.getEncoder());
|
||||
iso.updateValue(4, "INVALID!");
|
||||
throw new RuntimeException("Update failed!");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.solab.iso8583;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Extended test for LLLLVar field
|
||||
*
|
||||
* @author Peter Margetiak
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class TestLlllPatch {
|
||||
|
||||
private MessageFactory<IsoMessage> mfact = new MessageFactory<>();
|
||||
private int fieldLength;
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static List<Integer> lengths() {
|
||||
return Arrays.asList(5, 50, 500, 5000);
|
||||
}
|
||||
|
||||
public TestLlllPatch(int l) {
|
||||
fieldLength = l;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException {
|
||||
mfact.setConfigPath("issue50.xml");
|
||||
mfact.setAssignDate(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingLength() throws Exception {
|
||||
// prepare
|
||||
String llllvar = makeLLLLVar(fieldLength);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("01004000000000000000")
|
||||
.append(String.format("%04d", fieldLength)).append(llllvar);
|
||||
|
||||
// parse
|
||||
IsoMessage m = mfact.parseMessage(sb.toString().getBytes(), 0);
|
||||
Assert.assertNotNull(m);
|
||||
String f2 = m.getObjectValue(2);
|
||||
Assert.assertEquals(llllvar, f2);
|
||||
Assert.assertEquals(fieldLength, f2.length());
|
||||
//Encode
|
||||
m = mfact.newMessage(0x100);
|
||||
m.setIsoHeader(null);
|
||||
m.setValue(2, llllvar, IsoType.LLLLVAR, 0);
|
||||
Assert.assertEquals(sb.toString(), m.debugString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerialiseParseSmall() throws Exception {
|
||||
testSerialiseParse(88);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerialiseParseMedium() throws Exception {
|
||||
testSerialiseParse(258);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerialiseParseLarge() throws Exception {
|
||||
testSerialiseParse(9919);
|
||||
}
|
||||
|
||||
private void testSerialiseParse(final int LENGTH) throws Exception {
|
||||
// prepare
|
||||
String LLLLVar = makeLLLLVar(LENGTH);
|
||||
IsoMessage m = mfact.newMessage(0x100);
|
||||
m.setValue(2, LLLLVar, IsoType.LLLLVAR, 0);
|
||||
m.setBinary(true);
|
||||
|
||||
// write
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
m.write(bout, 2);
|
||||
bout.close();
|
||||
|
||||
// read
|
||||
byte[] buf = new byte[2];
|
||||
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
|
||||
bin.read(buf);
|
||||
Assert.assertNotEquals(buf, new byte[2]);
|
||||
|
||||
int len = ((buf[0] & 0xff) << 8) | (buf[1] & 0xff);
|
||||
buf = new byte[len];
|
||||
bin.read(buf);
|
||||
bin.close();
|
||||
|
||||
// parse
|
||||
mfact.setUseBinaryMessages(true);
|
||||
m = mfact.parseMessage(buf, mfact.getIsoHeader(0x100).length());
|
||||
Assert.assertNotNull(m);
|
||||
Assert.assertEquals(LLLLVar, m.getObjectValue(2));
|
||||
}
|
||||
|
||||
private String makeLLLLVar(final int length) {
|
||||
final char[] chars = new char[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
chars[i] = 'a';
|
||||
}
|
||||
|
||||
return new String(chars);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ public class TestParsing {
|
||||
|
||||
@Before
|
||||
public void init() throws IOException {
|
||||
mf = new MessageFactory<IsoMessage>();
|
||||
mf = new MessageFactory<>();
|
||||
mf.setCharacterEncoding("UTF-8");
|
||||
mf.setCustomField(48, new CustomField48());
|
||||
mf.setConfigPath("config.xml");
|
||||
@@ -108,19 +108,6 @@ public class TestParsing {
|
||||
Assert.assertEquals(123456, val.getValue().intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComposite() throws ParseException, UnsupportedEncodingException {
|
||||
IsoMessage m = mf.parseMessage("12000040000000000000016one 03two12345.".getBytes(), 0);
|
||||
Assert.assertNotNull(m);
|
||||
CompositeField f = m.getObjectValue(10);
|
||||
Assert.assertNotNull(f);
|
||||
Assert.assertEquals(4, f.getValues().size());
|
||||
Assert.assertEquals("one ", f.getObjectValue(0));
|
||||
Assert.assertEquals("two", f.getObjectValue(1));
|
||||
Assert.assertEquals("12345", f.getObjectValue(2));
|
||||
Assert.assertEquals(".", f.getObjectValue(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDates() throws ParseException, UnsupportedEncodingException {
|
||||
IsoMessage m = mf.parseMessage("060002000000000000000125213456".getBytes(), 0);
|
||||
@@ -132,20 +119,34 @@ public class TestParsing {
|
||||
Assert.assertEquals(Calendar.JANUARY, cal.get(Calendar.MONTH));
|
||||
Assert.assertEquals(25, cal.get(Calendar.DATE));
|
||||
Assert.assertEquals(21, cal.get(Calendar.HOUR_OF_DAY));
|
||||
mf.setTimezoneForParseGuide(0x600, 7, TimeZone.getTimeZone("GMT"));
|
||||
Assert.assertEquals("debug string should match", "060002000000000000000125213456", m.debugString());
|
||||
mf.setTimezoneForParseGuide(0x600, 7, TimeZone.getTimeZone("UTC"));
|
||||
m = mf.parseMessage("060002000000000000000125213456".getBytes(), 0);
|
||||
f = m.getObjectValue(7);
|
||||
cal.setTime(f);
|
||||
Assert.assertEquals(Calendar.JANUARY, cal.get(Calendar.MONTH));
|
||||
Assert.assertEquals(25, cal.get(Calendar.DATE));
|
||||
Assert.assertEquals(15, cal.get(Calendar.HOUR_OF_DAY));
|
||||
Assert.assertEquals("Hour of day mismatch", 15, cal.get(Calendar.HOUR_OF_DAY));
|
||||
Assert.assertEquals(TimeZone.getTimeZone("UTC"), m.getField(7).getTimeZone());
|
||||
mf.setTimezoneForParseGuide(0x600, 7, TimeZone.getTimeZone("GMT+0100"));
|
||||
m = mf.parseMessage("060002000000000000000125213456".getBytes(), 0);
|
||||
f = m.getObjectValue(7);
|
||||
cal.setTime(f);
|
||||
Assert.assertEquals(Calendar.JANUARY, cal.get(Calendar.MONTH));
|
||||
Assert.assertEquals(25, cal.get(Calendar.DATE));
|
||||
Assert.assertEquals(14, cal.get(Calendar.HOUR_OF_DAY));
|
||||
Assert.assertEquals("Hour of day mismatch", 14, cal.get(Calendar.HOUR_OF_DAY));
|
||||
Assert.assertEquals(TimeZone.getTimeZone("GMT+0100"), m.getField(7).getTimeZone());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTimezoneInResponse() throws ParseException, IOException {
|
||||
mf.setTimezoneForParseGuide(0x600, 7, TimeZone.getTimeZone("UTC"));
|
||||
IsoMessage m = mf.parseMessage("060002000000000000000125213456".getBytes(), 0);
|
||||
Assert.assertEquals(TimeZone.getTimeZone("UTC"), m.getField(7).getTimeZone());
|
||||
IsoMessage r = mf.createResponse(m);
|
||||
Assert.assertEquals(0x610, r.getType());
|
||||
Assert.assertTrue(r.hasField(7));
|
||||
Assert.assertTrue(m.getField(7) != r.getField(7));
|
||||
Assert.assertEquals(TimeZone.getTimeZone("UTC"), r.getField(7).getTimeZone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.solab.iso8583;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* Test timezone assignment in date/time fields.
|
||||
*
|
||||
* @author Enrique Zamudio
|
||||
* Date: 4/17/18 11:50 AM
|
||||
*/
|
||||
public class TestTimezones {
|
||||
|
||||
private final TimeZone utc = TimeZone.getTimeZone("UTC");
|
||||
private final TimeZone gmt5 = TimeZone.getTimeZone("GMT-500");
|
||||
private MessageFactory<IsoMessage> mf;
|
||||
|
||||
@Before
|
||||
public void init() throws IOException {
|
||||
mf = new MessageFactory<>();
|
||||
mf.setCharacterEncoding("UTF-8");
|
||||
mf.setConfigPath("timezones.xml");
|
||||
mf.setAssignDate(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTemplate() {
|
||||
IsoMessage t = mf.getMessageTemplate(0x100);
|
||||
Assert.assertTrue(t.hasEveryField(7, 12, 13));
|
||||
Assert.assertEquals(utc, t.getField(7).getTimeZone());
|
||||
Assert.assertEquals(gmt5, t.getField(12).getTimeZone());
|
||||
Assert.assertNull(t.getField(13).getTimeZone());
|
||||
t = mf.newMessage(0x100);
|
||||
Assert.assertTrue(t.hasField(7));
|
||||
Assert.assertEquals(utc, t.getField(7).getTimeZone());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingGuide() throws ParseException, IOException {
|
||||
String trama = "011002180000000000001231112233112233112233";
|
||||
IsoMessage m = mf.parseMessage(trama.getBytes(), 0);
|
||||
Assert.assertTrue(m.hasEveryField(7, 12, 13));
|
||||
Assert.assertEquals(utc, m.getField(7).getTimeZone());
|
||||
Assert.assertEquals(gmt5, m.getField(12).getTimeZone());
|
||||
Assert.assertNull(m.getField(13).getTimeZone());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,6 +25,8 @@ public class CustomBinCodecs {
|
||||
private final BigInteger b29 = new BigInteger("12345678901234567890123456789");
|
||||
final byte[] longData2 = new byte[]{ 0x12, 0x34, 0x56, 0x78, (byte)0x90, 00, 00, 00, 00, 00 };
|
||||
final byte[] bigintData1 = new byte[]{ 1, 0x23, 0x45, 0x67, (byte)0x89, 1, 0x23, 0x45, 0x67, (byte)0x89, 1, 0x23, 0x45, 0x67, (byte)0x89, 00, 00, 00, 00, 00 };
|
||||
final byte[] rlong = new byte[]{ 0x12, 0x34, 0x56, 0x78, (byte)0x9F };
|
||||
final byte[] rbi = new byte[]{ 0x12, 0x34, 0x56, 0x78, (byte)0x90, 0x12, 0x34, 0x56, 0x78, (byte)0x9F };
|
||||
|
||||
@Test
|
||||
public void testLongCodec() {
|
||||
@@ -98,4 +100,11 @@ public class CustomBinCodecs {
|
||||
testFieldType(IsoType.LLLBIN, new LllbinParseInfo(), 12, 19);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRightPadding() {
|
||||
final LongBcdCodec lc = new LongBcdCodec(true);
|
||||
Assert.assertEquals(123456789L, (long) lc.decodeBinaryField(rlong, 0, 5));
|
||||
final BigIntBcdCodec bc = new BigIntBcdCodec(true);
|
||||
Assert.assertEquals(new BigInteger("1234567890123456789"), bc.decodeBinaryField(rbi, 0, 10));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public class TestComposites {
|
||||
@Test
|
||||
public void testEncodeText() {
|
||||
final CompositeField f = new CompositeField();
|
||||
f.addValue(new IsoValue<String>(IsoType.ALPHA, "One", 5));
|
||||
f.addValue(new IsoValue<>(IsoType.ALPHA, "One", 5));
|
||||
f.getValues().get(0).setCharacterEncoding("UTF-8");
|
||||
Assert.assertEquals("One ", f.encodeField(f));
|
||||
f.addValue("Two", null, IsoType.LLVAR, 0);
|
||||
@@ -37,13 +37,13 @@ public class TestComposites {
|
||||
@Test
|
||||
public void testEncodeBinary() {
|
||||
final CompositeField f = new CompositeField()
|
||||
.addValue(new IsoValue<String>(IsoType.ALPHA, "One", 5));
|
||||
.addValue(new IsoValue<>(IsoType.ALPHA, "One", 5));
|
||||
Assert.assertArrayEquals(new byte[]{'O', 'n', 'e', 32, 32}, f.encodeBinaryField(f));
|
||||
f.addValue(new IsoValue<String>(IsoType.LLVAR, "Two"));
|
||||
f.addValue(new IsoValue<>(IsoType.LLVAR, "Two"));
|
||||
Assert.assertArrayEquals(new byte[]{'O', 'n', 'e', ' ', ' ', 3, 'T', 'w', 'o'},
|
||||
f.encodeBinaryField(f));
|
||||
f.addValue(new IsoValue<Long>(IsoType.NUMERIC, 999l, 5));
|
||||
f.addValue(new IsoValue<String>(IsoType.ALPHA, "X", 1));
|
||||
f.addValue(new IsoValue<>(IsoType.NUMERIC, 999l, 5));
|
||||
f.addValue(new IsoValue<>(IsoType.ALPHA, "X", 1));
|
||||
Assert.assertArrayEquals(binaryData, f.encodeBinaryField(f));
|
||||
}
|
||||
|
||||
@@ -79,4 +79,19 @@ public class TestComposites {
|
||||
Assert.assertEquals("X", f.getValues().get(3).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeBinaryWithOffset() {
|
||||
final CompositeField dec = new CompositeField()
|
||||
.addParser(new LlvarParseInfo())
|
||||
.addParser(new NumericParseInfo(5))
|
||||
.addParser(new AlphaParseInfo(1));
|
||||
int offset = 5;
|
||||
final CompositeField f = dec.decodeBinaryField(binaryData, offset, this.binaryData.length - offset);
|
||||
Assert.assertNotNull(f);
|
||||
Assert.assertEquals(3, f.getValues().size());
|
||||
Assert.assertEquals("Two", f.getValues().get(0).getValue());
|
||||
Assert.assertEquals(999l, f.getValues().get(1).getValue());
|
||||
Assert.assertEquals("X", f.getValues().get(2).getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -48,4 +48,47 @@ public class TestDates {
|
||||
Assert.assertEquals(comp.getValue().getTime(), bin.getValue().getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDate12FutureTolerance() throws ParseException, IOException {
|
||||
Date soon = new Date(System.currentTimeMillis() + 50000);
|
||||
byte[] buf = IsoType.DATE12.format(soon, null).getBytes();
|
||||
IsoValue<Date> comp = new Date12ParseInfo().parse(0, buf, 0, null);
|
||||
assert comp.getValue().after(new Date());
|
||||
//Now with the binary
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
comp.write(bout, true, false);
|
||||
IsoValue<Date> bin = new Date12ParseInfo().parseBinary(0, bout.toByteArray(), 0, null);
|
||||
Assert.assertEquals(comp.getValue().getTime(), bin.getValue().getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDate14FutureTolerance() throws ParseException, IOException {
|
||||
Date soon = new Date(System.currentTimeMillis() + 50000);
|
||||
byte[] buf = IsoType.DATE14.format(soon, null).getBytes();
|
||||
IsoValue<Date> comp = new Date14ParseInfo().parse(0, buf, 0, null);
|
||||
assert comp.getValue().after(new Date());
|
||||
//Now with the binary
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
comp.write(bout, true, false);
|
||||
IsoValue<Date> bin = new Date14ParseInfo().parseBinary(0, bout.toByteArray(), 0, null);
|
||||
Assert.assertEquals(comp.getValue().getTime(), bin.getValue().getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDate6() throws ParseException, IOException {
|
||||
Date nye = new Date(1514700000000L);
|
||||
IsoValue<Date> f = new IsoValue<>(IsoType.DATE6, nye);
|
||||
Assert.assertEquals("171231", f.toString());
|
||||
IsoValue<Date> parsed = new Date6ParseInfo().parse(1, "171231".getBytes(), 0, null);
|
||||
Assert.assertEquals("171231", parsed.toString());
|
||||
Assert.assertEquals(f.getValue(), parsed.getValue());
|
||||
byte[] buf = new byte[3];
|
||||
buf[0] = 0x17;
|
||||
buf[1] = 0x12;
|
||||
buf[2] = 0x31;
|
||||
parsed = new Date6ParseInfo().parseBinary(2, buf, 0, null);
|
||||
Assert.assertEquals("171231", parsed.toString());
|
||||
Assert.assertEquals(f.getValue(), parsed.getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.solab.iso8583.util;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* BCD encoding tests.
|
||||
*
|
||||
@@ -29,6 +31,8 @@ public class TestBcd {
|
||||
Assert.assertArrayEquals(new byte[]{7, 0x79}, buf);
|
||||
Bcd.encode("999", buf);
|
||||
Assert.assertArrayEquals(new byte[]{9,(byte)0x99}, buf);
|
||||
Bcd.encodeRightPadded("999", buf);
|
||||
Assert.assertArrayEquals(new byte[]{(byte)0x99,(byte)0x9f}, buf);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -52,6 +56,11 @@ public class TestBcd {
|
||||
Assert.assertEquals(199, Bcd.decodeToLong(buf,0,4));
|
||||
buf[0]=9;
|
||||
Assert.assertEquals(999, Bcd.decodeToLong(buf,0,4));
|
||||
Assert.assertEquals(new BigInteger("999"), Bcd.decodeToBigInteger(buf,0,3));
|
||||
buf[0] = (byte)0x99;
|
||||
buf[1] = (byte)0x9f;
|
||||
Assert.assertEquals(999, Bcd.decodeRightPaddedToLong(buf,0,4));
|
||||
Assert.assertEquals(new BigInteger("999"), Bcd.decodeRightPaddedToBigInteger(buf,0,4));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE j8583-config PUBLIC "-//J8583//DTD CONFIG 1.0//EN"
|
||||
"http://j8583.sourceforge.net/j8583.dtd">
|
||||
<j8583-config>
|
||||
<!-- This is a test config file -->
|
||||
|
||||
<template type="100">
|
||||
<field num="10" type="LLLVAR">
|
||||
<field num="1" type="ALPHA" length="5">abcde</field>
|
||||
<field num="2" type="LLVAR">llvar</field>
|
||||
<field num="3" type="NUMERIC" length="5">12345</field>
|
||||
<field num="4" type="ALPHA" length="1">X</field>
|
||||
</field>
|
||||
</template>
|
||||
|
||||
<template type="101">
|
||||
<field num="10" type="LLLVAR">
|
||||
<field num="1" type="ALPHA" length="5">fghij</field>
|
||||
<field num="2" type="LLVAR">
|
||||
<field num="1" type="ALPHA" length="2">KL</field>
|
||||
<field num="2" type="ALPHA" length="2">mn</field>
|
||||
<field num="3" type="LLVAR">
|
||||
<field num="1" type="ALPHA" length="3">123</field>
|
||||
<field num="2" type="ALPHA" length="2">45</field>
|
||||
</field>
|
||||
</field>
|
||||
<field num="3" type="NUMERIC" length="5">67890</field>
|
||||
<field num="4" type="ALPHA" length="1">Y</field>
|
||||
</field>
|
||||
</template>
|
||||
|
||||
<template type="102" extends="101">
|
||||
<field num="12" type="LLLVAR">
|
||||
<field num="1" type="ALPHA" length="5">fghij</field>
|
||||
<field num="2" type="LLVAR">
|
||||
<field num="1" type="ALPHA" length="2">KL</field>
|
||||
<field num="2" type="ALPHA" length="2">mn</field>
|
||||
<field num="3" type="LLVAR">
|
||||
<field num="1" type="ALPHA" length="3">123</field>
|
||||
<field num="2" type="ALPHA" length="2">45</field>
|
||||
</field>
|
||||
</field>
|
||||
<field num="3" type="NUMERIC" length="5">67890</field>
|
||||
<field num="4" type="ALPHA" length="1">Y</field>
|
||||
</field>
|
||||
</template>
|
||||
|
||||
<parse type="100">
|
||||
<field num="10" type="LLLVAR">
|
||||
<field num="1" type="ALPHA" length="5" />
|
||||
<field num="2" type="LLVAR"/>
|
||||
<field num="3" type="NUMERIC" length="5" />
|
||||
<field num="4" type="ALPHA" length="1" />
|
||||
</field>
|
||||
</parse>
|
||||
|
||||
<parse type="101">
|
||||
<field num="10" type="LLLVAR">
|
||||
<field num="1" type="ALPHA" length="5" />
|
||||
<field num="2" type="LLVAR">
|
||||
<field num="1" type="ALPHA" length="2" />
|
||||
<field num="2" type="ALPHA" length="2" />
|
||||
<field num="3" type="LLVAR">
|
||||
<field num="1" type="ALPHA" length="3" />
|
||||
<field num="2" type="ALPHA" length="2" />
|
||||
</field>
|
||||
</field>
|
||||
<field num="4" type="ALPHA" length="1" />
|
||||
</field>
|
||||
</parse>
|
||||
|
||||
</j8583-config>
|
||||
@@ -11,6 +11,7 @@
|
||||
<header type="0410" ref="0210" />
|
||||
<header type="0800">ISO015000015</header>
|
||||
<header type="0810" ref="0800"/>
|
||||
<header type="0280" binary="true">ffffffff</header>
|
||||
|
||||
<!-- The client example uses this to create requests -->
|
||||
<template type="0200">
|
||||
@@ -54,6 +55,9 @@
|
||||
<field num="65" type="LLLBIN">01234567865</field>
|
||||
<field num="102" type="LLLVAR">Field of type LLLVAR</field>
|
||||
</template>
|
||||
<template type="0280">
|
||||
<field num="3" type="NUMERIC" length="2">99</field>
|
||||
</template>
|
||||
|
||||
<!-- The server example uses this to read the requests -->
|
||||
<parse type="0200">
|
||||
@@ -132,13 +136,14 @@
|
||||
<field num="39" type="ALPHA" length="2"/>
|
||||
</parse>
|
||||
|
||||
<parse type="1200">
|
||||
<field num="10" type="LLLVAR">
|
||||
<field num="1" type="ALPHA" length="5" />
|
||||
<field num="2" type="LLVAR"/>
|
||||
<field num="3" type="NUMERIC" length="5" />
|
||||
<field num="4" type="ALPHA" length="1" />
|
||||
</field>
|
||||
<parse type="0201">
|
||||
<field num="3" type="NUMERIC" length="19" />
|
||||
</parse>
|
||||
<parse type="0202">
|
||||
<field num="3" type="NUMERIC" length="22" />
|
||||
</parse>
|
||||
<parse type="0280">
|
||||
<field num="3" type="NUMERIC" length="2" />
|
||||
</parse>
|
||||
|
||||
</j8583-config>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<j8583-config>
|
||||
|
||||
<!-- Parent -->
|
||||
<parse type="000">
|
||||
<field num="4" type="AMOUNT" />
|
||||
</parse>
|
||||
|
||||
<parse type="0200" extends="000">
|
||||
<field num="62" type="LLLVAR">
|
||||
<field num="4" type="ALPHA" length="13" />
|
||||
</field>
|
||||
</parse>
|
||||
|
||||
</j8583-config>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<j8583-config>
|
||||
<header type="100">ISO999999999</header>
|
||||
<parse type="100">
|
||||
<field num="2" type="LLLLVAR" />
|
||||
</parse>
|
||||
</j8583-config>
|
||||
@@ -0,0 +1,7 @@
|
||||
<j8583-config>
|
||||
|
||||
<template type="0200">
|
||||
<field num="3" type="NUMERIC" length="6"></field>
|
||||
</template>
|
||||
|
||||
</j8583-config>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<Configuration>
|
||||
<Appenders>
|
||||
<Console name="console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="%d{ABSOLUTE} [%t] %-5p %m%n" />
|
||||
</Console>
|
||||
</Appenders>
|
||||
|
||||
<Loggers>
|
||||
<Logger name="io.netty" level="info"/>
|
||||
|
||||
<Root level="trace">
|
||||
<AppenderRef ref="console" />
|
||||
</Root>
|
||||
</Loggers>
|
||||
|
||||
</Configuration>
|
||||
@@ -0,0 +1,15 @@
|
||||
<j8583-config>
|
||||
|
||||
<template type="0100">
|
||||
<field num="7" type="DATE10" tz="UTC" />
|
||||
<field num="12" type="TIME" tz="GMT-500" />
|
||||
<field num="13" type="TIME" />
|
||||
</template>
|
||||
|
||||
<parse type="0110">
|
||||
<field num="7" type="DATE10" tz="UTC" />
|
||||
<field num="12" type="TIME" tz="GMT-500" />
|
||||
<field num="13" type="TIME" />
|
||||
</parse>
|
||||
|
||||
</j8583-config>
|
||||
Reference in New Issue
Block a user