Skip to content

Commit 44a2e3a

Browse files
zubriclaude
andauthored
(PW-3126) Fix BIC branch extraction from DN (#297)
* fix(PW-3126): fix DN to BIC extraction to include branch from ou component Added parseBranch() to DistinguishedName to extract the branch code from the ou field of a SWIFT DN string, selecting the rightmost ou when multiple are present (closest to o=<bic8>). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(PW-3126): use BIC class in parseBIC to produce a valid BIC11 including branch parseBIC now constructs a BIC object from the extracted BIC8, sets the branch from the ou component via parseBranch, and returns getBic11(). When no branch is present XXX is used as default branch code. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(PW-3126): ignore ou components with invalid length when parsing branch from DN parseBranch now skips ou values that are not exactly 3 characters, gracefully handling malformed DNs without throwing or returning incorrect branch codes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 49cbfaf commit 44a2e3a

File tree

3 files changed

+116
-5
lines changed

3 files changed

+116
-5
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.10 - SNAPSHOT
4+
* (PW-3126) Fixed DN to BIC extraction in `DistinguishedName` to include the branch code from the `ou` component
5+
36
#### 10.3.9 - February 2026
47
* Deprecated `Field.validatorPattern()` - validation patterns moved to Integrator's `FieldPatternRegistry`
58
* Updated MT message structures from schema regeneration (MT081, MT513-MT543, MT586, MT920, MT942)

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

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.prowidesoftware.swift.model;
22

33
import org.apache.commons.lang3.StringUtils;
4+
import org.apache.commons.lang3.Strings;
45

56
/**
67
* Represents a Distinguished Name (DN) in the context of a directory service.
@@ -57,16 +58,69 @@ public String toString() {
5758
return dn.toString();
5859
}
5960

61+
/**
62+
* Parses a BIC11 from a SWIFT Distinguished Name string.
63+
*
64+
* <p>Extracts the institution BIC8 from the {@code o=} component and the branch from the
65+
* {@code ou=} component. The result is always an 11-character BIC: if no branch is present
66+
* in the DN, {@code XXX} is used as the default branch code.
67+
*
68+
* <p>When multiple {@code ou} components are present, the one closest to {@code o=&lt;bic8&gt;}
69+
* (rightmost) is used as the branch. See {@link #parseBranch(String)} for details.
70+
*
71+
* @param dn the Distinguished Name string, may be null or blank
72+
* @return the BIC11 in uppercase, or null if no BIC8 component is found or the input is blank
73+
* @see #parseBranch(String)
74+
*/
6075
public static String parseBIC(final String dn) {
6176
if (StringUtils.isBlank(dn)) {
6277
return null;
6378
}
79+
String bic8 = null;
80+
for (String s : StringUtils.split(dn, ",")) {
81+
if (Strings.CS.startsWith(s, "o=") && !Strings.CS.equals(s, "o=swift")) {
82+
bic8 = StringUtils.upperCase(StringUtils.substringAfter(s, "o="));
83+
break;
84+
}
85+
}
86+
if (bic8 == null) {
87+
return null;
88+
}
89+
BIC bic = new BIC(bic8);
90+
String branch = parseBranch(dn);
91+
if (branch != null) {
92+
bic.setBranch(branch);
93+
}
94+
return bic.getBic11();
95+
}
96+
97+
/**
98+
* Parses the branch code from a SWIFT Distinguished Name string.
99+
*
100+
* <p>The branch is represented by the {@code ou} component. Only {@code ou} values with exactly
101+
* 3 characters are considered valid; others are silently ignored. When multiple valid {@code ou}
102+
* components are present, the one closest to {@code o=&lt;bic8&gt;} (i.e. the rightmost) is used,
103+
* as DNs are read right-to-left from least to most specific:
104+
* <pre>cn=a,ou=dept,ou=bbb,o=biccode,o=swift → branch is "BBB"</pre>
105+
*
106+
* @param dn the Distinguished Name string, may be null or blank
107+
* @return the branch code in uppercase, or null if not present, invalid, or if the input is blank
108+
*/
109+
protected static String parseBranch(final String dn) {
110+
if (StringUtils.isBlank(dn)) {
111+
return null;
112+
}
113+
String branch = null;
64114
for (String s : StringUtils.split(dn, ",")) {
65-
if (StringUtils.startsWith(s, "o=") && !StringUtils.equals(s, "o=swift")) {
66-
return StringUtils.upperCase(StringUtils.substringAfter(s, "o="));
115+
if (Strings.CS.startsWith(s, "ou=")) {
116+
String value = StringUtils.substringAfter(s, "ou=");
117+
if (value.length() == 3) {
118+
// keep iterating — last valid match is the rightmost (closest to o=<bic8>)
119+
branch = value;
120+
}
67121
}
68122
}
69-
return null;
123+
return branch != null ? StringUtils.upperCase(branch) : null;
70124
}
71125

72126
/**

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

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,69 @@ void testParseBIC() {
4646
assertNull(DistinguishedName.parseBIC(dn1));
4747

4848
dn1 = "o=bic8code,o=swift";
49-
assertEquals("BIC8CODE", DistinguishedName.parseBIC(dn1));
49+
assertEquals("BIC8CODEXXX", DistinguishedName.parseBIC(dn1));
5050

5151
dn1 = "ou=bar,o=bic8code,o=swift";
52-
assertEquals("BIC8CODE", DistinguishedName.parseBIC(dn1));
52+
assertEquals("BIC8CODEBAR", DistinguishedName.parseBIC(dn1));
5353

5454
assertNull(DistinguishedName.parseBIC(null));
5555

5656
assertNull(DistinguishedName.parseBIC(""));
5757

5858
assertNull(DistinguishedName.parseBIC("XXX"));
5959
}
60+
61+
@Test
62+
void testParseBranchSingleOU() {
63+
assertEquals("XXX", DistinguishedName.parseBranch("ou=xxx,o=biccode,o=swift"));
64+
}
65+
66+
@Test
67+
void testParseBranchUppercasesResult() {
68+
assertEquals("XXX", DistinguishedName.parseBranch("ou=XXX,o=biccode,o=swift"));
69+
}
70+
71+
@Test
72+
void testParseBranchWithCN() {
73+
assertEquals("XXX", DistinguishedName.parseBranch("cn=a,ou=xxx,o=biccode,o=swift"));
74+
}
75+
76+
@Test
77+
void testParseBranchMultipleOU() {
78+
// rightmost ou (closest to o=<bic8>) is the branch; leftmost is a subdivision
79+
assertEquals("BBB", DistinguishedName.parseBranch("cn=a,ou=dept,ou=bbb,o=biccode,o=swift"));
80+
}
81+
82+
@Test
83+
void testParseBranchNoOU() {
84+
assertNull(DistinguishedName.parseBranch("o=biccode,o=swift"));
85+
}
86+
87+
@Test
88+
void testParseBranchNull() {
89+
assertNull(DistinguishedName.parseBranch(null));
90+
}
91+
92+
@Test
93+
void testParseBranchBlank() {
94+
assertNull(DistinguishedName.parseBranch(""));
95+
assertNull(DistinguishedName.parseBranch(" "));
96+
}
97+
98+
@Test
99+
void testParseBranchNoMatch() {
100+
assertNull(DistinguishedName.parseBranch("o=biccode,o=swift"));
101+
}
102+
103+
@Test
104+
void testParseBranchIgnoresInvalidOULength() {
105+
// ou values with length != 3 must be ignored
106+
assertNull(DistinguishedName.parseBranch("ou=toolong,o=biccode,o=swift"));
107+
assertNull(DistinguishedName.parseBranch("ou=ab,o=biccode,o=swift"));
108+
assertNull(DistinguishedName.parseBranch("ou=x,o=biccode,o=swift"));
109+
// valid ou present alongside invalid ones — valid one wins
110+
assertEquals("XXX", DistinguishedName.parseBranch("ou=toolong,ou=xxx,o=biccode,o=swift"));
111+
// multiple invalid ou, no valid one
112+
assertNull(DistinguishedName.parseBranch("ou=ab,ou=toolong,o=biccode,o=swift"));
113+
}
60114
}

0 commit comments

Comments
 (0)