Skip to content

Commit ee9f254

Browse files
zubriclaude
andauthored
(CU-86b8v3uxa) Fix metadata extraction for ACK followed by MT Output (#299)
When an ACK (service 21) message has an appended MT with block 2 Output, the metadata (identifier, direction, MIR, UUID, UETR, etc.) is now extracted from the appended MT instead of defaulting to ACK. For ACK followed by MT Input the existing behavior is preserved. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 73b74ed commit ee9f254

File tree

4 files changed

+85
-15
lines changed

4 files changed

+85
-15
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Prowide Core - CHANGELOG
22

3+
### 10.3.11 - SNAPSHOT
4+
* Fix: `MtSwiftMessage` created from ACK (service 21) followed by MT with block 2 Output now extracts the message type and metadata from the appended MT instead of defaulting to ACK
5+
36
#### 10.3.10 - March 2026
47
* Migrated deprecated `StringUtils` methods to `Strings.CS` equivalents (equals, startsWith, endsWith, contains, replace, indexOf, lastIndexOf, remove, removeEnd)
58
* (PW-3126) Fixed DN to BIC extraction in `DistinguishedName` to include the branch code from the `ou` component

src/main/java/com/prowidesoftware/swift/model/MtSwiftMessage.java

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,9 @@ public MtSwiftMessage(final String fin) {
8181
* content will be stored in the message attribute but the metadata (such as the message type) will be extracted
8282
* from the first message only.
8383
*
84-
* <p>Notice that if an ACK/NAK message is used as parameter, this object will represent the ACK/NAK.
85-
* Even if the original message is attached after the service 21 messages.
84+
* <p>If an ACK/NAK (service 21) message is used as parameter and includes an appended MT with block 2 Output,
85+
* the metadata is extracted from the appended MT. For ACK/NAK with an appended MT with block 2 Input or without
86+
* an appended MT, the message is represented as ACK/NAK.
8687
*
8788
* <p>File format is set to {@link FileFormat#FIN}
8889
*
@@ -268,25 +269,46 @@ protected void updateFromMessage(MessageMetadataStrategy metadataStrategy) throw
268269
* This method extracts the metadata from the model and sets the internal attributes accordingly, using the
269270
* provided {@link MessageMetadataStrategy}. And on top of that, sets additional attributes such as the
270271
* last modified date, checksum, and MUR.
271-
*
272+
* <p>
273+
* For ACK/NAK (service 21) messages with an appended MT that has block 2 Output, the metadata is extracted
274+
* from the appended MT rather than from the ACK/NAK prefix.
272275
*
273276
* @param model the SwiftMessage model to extract metadata from
274277
* @param metadataStrategy a strategy implementation to extract the metadata from the model
275278
*/
276279
private void updateAttributes(final SwiftMessage model, final MessageMetadataStrategy metadataStrategy) {
277-
applyStrategy(model, metadataStrategy);
280+
// for ACK/NAK with an appended MT Output, use the appended MT as the effective model for metadata extraction
281+
final SwiftMessage effectiveModel = resolveEffectiveModel(model);
282+
283+
applyStrategy(effectiveModel, metadataStrategy);
278284

279285
setFileFormat(FileFormat.FIN);
280286
setLastModified(Calendar.getInstance());
281-
setMur(model.getMUR());
282-
setPde(model.getPDE());
283-
setPdm(model.getPDM());
284-
setMir(model.getMIR());
285-
if (model.getBlock2() != null) {
286-
setUuid(model.getUUID());
287+
setMur(effectiveModel.getMUR());
288+
setPde(effectiveModel.getPDE());
289+
setPdm(effectiveModel.getPDM());
290+
setMir(effectiveModel.getMIR());
291+
if (effectiveModel.getBlock2() != null) {
292+
setUuid(effectiveModel.getUUID());
293+
}
294+
setDirection(effectiveModel.getDirection());
295+
setUetr(effectiveModel.getUETR());
296+
}
297+
298+
/**
299+
* For ACK/NAK (service 21) messages with an appended MT that has block 2 Output, returns the appended MT model.
300+
* Otherwise, returns the original model unchanged.
301+
*/
302+
private SwiftMessage resolveEffectiveModel(final SwiftMessage model) {
303+
if (model.isServiceMessage21() && model.getUnparsedTextsSize() > 0) {
304+
final SwiftMessage original = model.getUnparsedTexts().getTextAsMessage(0);
305+
if (original != null
306+
&& original.getBlock2() != null
307+
&& original.getBlock2().isOutput()) {
308+
return original;
309+
}
287310
}
288-
setDirection(model.getDirection());
289-
setUetr(model.getUETR());
311+
return model;
290312
}
291313

292314
/**

src/main/java/com/prowidesoftware/swift/model/SwiftMessageUtils.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,9 @@ public static String receiver(final SwiftMessage m) {
357357
* Get a string in the form of businessprocess.messagetype.variant
358358
*
359359
* <p>
360-
* For acknowledges and negative acknowledges, the identifier is fixed to "ACK" and "NAK" respectively.
360+
* For acknowledges and negative acknowledges with an appended MT with block 2 Input, the identifier is fixed
361+
* to "ACK" and "NAK" respectively. However, if the appended MT has block 2 Output, the identifier is extracted
362+
* from the appended MT message type.
361363
*
362364
* @return a string with the MT message type identification
363365
* @since 9.3.19
@@ -366,8 +368,16 @@ public static String identifier(final SwiftMessage m) {
366368
if (m == null) {
367369
return null;
368370
}
369-
// for ACK/NAK messages, we return the fixed identifiers
371+
// for ACK/NAK messages with an appended MT with block 2 Output, we extract the identifier from the MT
370372
if (m.isServiceMessage21()) {
373+
if (m.getUnparsedTextsSize() > 0) {
374+
final SwiftMessage original = m.getUnparsedTexts().getTextAsMessage(0);
375+
if (original != null
376+
&& original.getBlock2() != null
377+
&& original.getBlock2().isOutput()) {
378+
return identifier(original);
379+
}
380+
}
371381
if (m.isAck()) {
372382
return MtSwiftMessage.IDENTIFIER_ACK;
373383
} else if (m.isNack()) {

src/test/java/com/prowidesoftware/swift/model/MtSwiftMessageTest.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ public void testParseAck() {
7070
}
7171

7272
@Test
73-
public void testParseAckWithOriginalMessageCopy() {
73+
public void testParseAckWithOriginalMessageCopyInput() {
74+
// ACK followed by MT with block 2 Input -> identifier should be ACK
7475
String fin =
7576
"{1:F21AAAALT2XAXXX0000000000}{4:{177:1903250612}{451:0}}{1:F01AAAALT2XAXXX1012000002}{2:I103BBBBARZZXXXXN}{4:\n"
7677
+ ":20:TEST\n"
@@ -101,6 +102,40 @@ public void testParseAckWithOriginalMessageCopy() {
101102
// the original message has an amount, we extract it
102103
assertEquals(new BigDecimal("23453"), mt.getAmount());
103104
assertEquals("USD", mt.getCurrency());
105+
106+
// ACK followed by MT Input keeps the ACK identifier
107+
assertEquals("ACK", mt.getIdentifier());
108+
}
109+
110+
@Test
111+
public void testParseAckWithOriginalMessageCopyOutput() {
112+
// ACK followed by MT with block 2 Output -> metadata should be extracted from the appended MT
113+
String fin =
114+
"{1:F21AAAALT2XAXXX0000000000}{4:{177:1903250612}{451:0}}{1:F01BBBBARZZAXXX1012000002}{2:O1031200190903AAAALT2XAXXX00000000001903250612N}{3:{121:d]8b27e4-5891-4649-b1b8-22a3c0985ab2}}{4:\n"
115+
+ ":20:TEST-OUT\n"
116+
+ ":32A:190903EUR12345,\n"
117+
+ "-}";
118+
MtSwiftMessage mt = MtSwiftMessage.parse(fin);
119+
120+
// identifier and message type are extracted from the appended MT, not "ACK"
121+
assertEquals("fin.103", mt.getIdentifier());
122+
assertEquals("103", mt.getMessageType());
123+
124+
// reference is extracted from the appended MT
125+
assertEquals("TEST-OUT", mt.getReference());
126+
127+
// direction is resolved from the appended MT's block 2 Output (incoming)
128+
assertEquals(MessageIOType.incoming, mt.getDirection());
129+
130+
// value date from the appended MT
131+
Calendar valueDate = mt.getValueDate();
132+
assertEquals(2019, valueDate.get(Calendar.YEAR));
133+
assertEquals(Calendar.SEPTEMBER, valueDate.get(Calendar.MONTH));
134+
assertEquals(3, valueDate.get(Calendar.DAY_OF_MONTH));
135+
136+
// amount from the appended MT
137+
assertEquals(new BigDecimal("12345"), mt.getAmount());
138+
assertEquals("EUR", mt.getCurrency());
104139
}
105140

106141
@Test

0 commit comments

Comments
 (0)