Skip to content

Commit

Permalink
PW-1875: ZuluTime format for BAH1 (#114)
Browse files Browse the repository at this point in the history
* Add ZuluDateTimeAdapter for BAH1

* Remove .000 of fractional seconds.

* Add Zulu in other xml() method

* Fix code

* Fix code

* Set adapters into xml method

* Add marshall test to check Zulu Date

* Reset values into the MxWriteParams

* Add CHANGELOG entry

* Fix PR Comments

* javadoc

---------

Co-authored-by: zubri <[email protected]>
  • Loading branch information
fernando-prowide and zubri authored May 21, 2024
1 parent 561f881 commit e05e4ad
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ lib
release.sh
.idea/sonarlint
*.hprof
.idea/copilot
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Prowide ISO 20022 - CHANGELOG

#### 9.4.4 - Jannuary 2024

#### 9.4.5 - SNAPSHOT
* (PW-1875) Changed the BusinessApplicationHeaderV01 marshaller to always use Zulu timezone with "Z" indicator

#### 9.4.4 - January 2024
* Enhanced the identifier extraction of the MxSwiftMessage to use the AppHdr when the Document namespace is missing
* Enhanced the generic AbstractMX#parse to detect the message type from the AppHdr when the Document namespace is missing
* Added default metadata extraction implementation for pacs and camt amounts and value dates
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import com.prowidesoftware.deprecation.DeprecationUtils;
import com.prowidesoftware.deprecation.ProwideDeprecated;
import com.prowidesoftware.deprecation.TargetYear;
import com.prowidesoftware.swift.model.mx.adapters.IsoDateTimeAdapter;
import com.prowidesoftware.swift.model.mx.adapters.ZuluDateTimeAdapter;
import com.prowidesoftware.swift.model.mx.dic.BusinessApplicationHeaderV01Impl;
import com.prowidesoftware.swift.model.mx.dic.Party9Choice;
import java.io.StringWriter;
Expand Down Expand Up @@ -219,6 +221,8 @@ public String xml(String prefix, boolean includeXMLDeclaration, EscapeHandler es
public String xml(MxWriteParams params) {
try {
JAXBContext context;
IsoDateTimeAdapter currentAdapter = params.adapters.dateTimeAdapter;
params.adapters.dateTimeAdapter = new IsoDateTimeAdapter(new ZuluDateTimeAdapter());
if (params.context != null) {
context = params.context;
} else {
Expand All @@ -237,6 +241,7 @@ public String xml(MxWriteParams params) {
params.escapeHandler,
params.indent);
marshaller.marshal(element, eventWriter);
params.adapters.dateTimeAdapter = currentAdapter;
return sw.getBuffer().toString();

} catch (JAXBException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright 2006-2023 Prowide
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.prowidesoftware.swift.model.mx.adapters;

import java.text.SimpleDateFormat;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.datatype.XMLGregorianCalendar;

/**
* XMLGregorianCalendar adapter for date time elements.
* <p>
* Marshals the date time as a Zulu time with format YYYY-MM-DDThh:mm:ss[.sss]Z which is aligned
* with ISO 8601. Dislike the default jaxb implementation, this adapter will always print the Z
* The fractional seconds is printed only when it is different from zero.
* <p>
* Notice the configured adapter in the model is the {@link IsoDateTimeAdapter} wrapper class, but you can pass this
* default implementation or your own in the constructor.
*
* @see TypeAdaptersConfiguration
* @since 9.4,5
*/
public class ZuluDateTimeAdapter extends XmlAdapter<String, XMLGregorianCalendar> {

private final SimpleDateFormat marshalFormat;
private final SimpleDateFormat unmarshalFormat;
private final XmlAdapter<String, XMLGregorianCalendar> customAdapterImpl;

/**
* Creates a date time adapter with the default format
*/
public ZuluDateTimeAdapter() {
this.marshalFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
this.unmarshalFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss[.SSS]['Z']");
this.customAdapterImpl = null;
}

/**
* Creates a time adapter with a specific given format that will be used for both the marshalling and unmarshalling
*/
public ZuluDateTimeAdapter(SimpleDateFormat dateFormat) {
this.marshalFormat = dateFormat;
this.unmarshalFormat = dateFormat;
this.customAdapterImpl = null;
}

/**
* Creates a date time adapter injecting a custom implementation
*/
public ZuluDateTimeAdapter(XmlAdapter<String, XMLGregorianCalendar> customAdapterImpl) {
this.marshalFormat = null;
this.unmarshalFormat = null;
this.customAdapterImpl = customAdapterImpl;
}

/**
* Creates a calendar parsing the value with this adapter format, or the default lexical representation as fallback.
*
* @param value the XML date time value to convert
* @return created calendar object or null if cannot be parsed
*/
@Override
public XMLGregorianCalendar unmarshal(String value) throws Exception {
if (this.customAdapterImpl != null) {
return this.customAdapterImpl.unmarshal(value);
} else {
return AdapterUtils.parse(this.unmarshalFormat, value);
}
}

/**
* Applies the configured format to the calendar.
*
* @param cal the model calendar to marshal
* @return formatted content for the XML
*/
@Override
public String marshal(XMLGregorianCalendar cal) throws Exception {
if (this.customAdapterImpl != null) {
return this.customAdapterImpl.marshal(cal);
} else {
String formatted;
synchronized (marshalFormat) {
// Viene un calendar no UTC?
formatted = AdapterUtils.format(this.marshalFormat, cal);
}
return formatted.replace(".000", "");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
* Copyright 2006-2023 Prowide
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.prowidesoftware.swift.model.mx.adapters;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.prowidesoftware.swift.model.mx.MxPacs00800110;
import java.math.BigDecimal;
import java.math.BigInteger;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import org.junit.jupiter.api.Test;

public class ZuluDateTimeAdapterTest {

private ZuluDateTimeAdapter adapter = new ZuluDateTimeAdapter();

@Test
public void testUnmarshallFractionOfSeconds() throws Exception {
XMLGregorianCalendar cal = adapter.unmarshal("2022-03-04T12:50:08.123Z");
assertEquals(2022, cal.getYear());
assertEquals(3, cal.getMonth());
assertEquals(4, cal.getDay());
assertEquals(12, cal.getHour());
assertEquals(50, cal.getMinute());
assertEquals(8, cal.getSecond());
assertEquals(new BigDecimal("0.123"), cal.getFractionalSecond());
assertEquals(0, cal.getTimezone());
}

@Test
public void testUnmarshallNoFractionOfSeconds() throws Exception {
XMLGregorianCalendar cal = adapter.unmarshal("2022-03-04T12:50:08Z");
assertEquals(2022, cal.getYear());
assertEquals(3, cal.getMonth());
assertEquals(4, cal.getDay());
assertEquals(12, cal.getHour());
assertEquals(50, cal.getMinute());
assertEquals(8, cal.getSecond());
assertEquals(null, cal.getFractionalSecond());
assertEquals(0, cal.getTimezone());
}

@Test
public void testUnmarshallNoOffset() throws Exception {
XMLGregorianCalendar cal = adapter.unmarshal("2022-03-04T12:50:08");
assertEquals(2022, cal.getYear());
assertEquals(3, cal.getMonth());
assertEquals(4, cal.getDay());
assertEquals(12, cal.getHour());
assertEquals(50, cal.getMinute());
assertEquals(8, cal.getSecond());
assertEquals(null, cal.getFractionalSecond());
}

@Test
public void testMarshallFractionOfSeconds() throws Exception {
XMLGregorianCalendar cal = DatatypeFactory.newInstance()
.newXMLGregorianCalendar(BigInteger.valueOf(2022), 3, 4, 12, 50, 8, new BigDecimal("0.123"), -180);
assertEquals("2022-03-04T12:50:08.123Z", adapter.marshal(cal));
}

@Test
public void testMarshallNoFractionOfSeconds() throws Exception {
XMLGregorianCalendar cal = DatatypeFactory.newInstance()
.newXMLGregorianCalendar(BigInteger.valueOf(2022), 3, 4, 12, 50, 8, null, -180);
assertEquals("2022-03-04T12:50:08Z", adapter.marshal(cal));
}

@Test
public void testMarshallNoOffset() throws Exception {
XMLGregorianCalendar cal = DatatypeFactory.newInstance()
.newXMLGregorianCalendar(BigInteger.valueOf(2022), 3, 4, 12, 50, 8, null, -0);
assertEquals("2022-03-04T12:50:08Z", adapter.marshal(cal));
}

@Test
public void test_marshall_message_with_BAH1_and_CreDt_Zulu() {

final String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + "<RequestPayload>\n"
+ "<h:AppHdr xmlns:h=\"urn:iso:std:iso:20022:tech:xsd:head.001.001.01\">\n"
+ " <h:Fr>\n"
+ " <h:FIId>\n"
+ " <h:FinInstnId>\n"
+ " <h:BICFI>FOOYVEC0XXX</h:BICFI>\n"
+ " </h:FinInstnId>\n"
+ " </h:FIId>\n"
+ " </h:Fr>\n"
+ " <h:To>\n"
+ " <h:FIId>\n"
+ " <h:FinInstnId>\n"
+ " <h:BICFI>FOOZUSC0XXX</h:BICFI>\n"
+ " </h:FinInstnId>\n"
+ " </h:FIId>\n"
+ " </h:To>\n"
+ " <h:BizMsgIdr>STRUCTUREDADDR</h:BizMsgIdr>\n"
+ " <h:MsgDefIdr>pacs.008.001.10</h:MsgDefIdr>\n"
+ " <h:CreDt>2024-03-27T20:45:56Z</h:CreDt>\n"
+ " <h:Prty>NORM</h:Prty>\n"
+ "</h:AppHdr>\n"
+ "<Doc:Document xmlns:Doc=\"urn:iso:std:iso:20022:tech:xsd:pacs.008.001.10\">\n"
+ "</Doc:Document>\n"
+ "</RequestPayload>";

// Create a new instance of MxPacs00800110 with AppHdr CreDtTm ZuluDateTime
MxPacs00800110 mxPacs00800110 = MxPacs00800110.parse(xml);

// After CreDtTm is parsed, it should be 2024-03-27T20:45:56Z
assertTrue(mxPacs00800110.message().contains("2024-03-27T20:45:56Z"));
}

@Test
public void test_marshall_message_with_BAH1_and_CreDt_OffsetDateTime() {

final String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + "<RequestPayload>\n"
+ "<h:AppHdr xmlns:h=\"urn:iso:std:iso:20022:tech:xsd:head.001.001.01\">\n"
+ " <h:Fr>\n"
+ " <h:FIId>\n"
+ " <h:FinInstnId>\n"
+ " <h:BICFI>FOOYVEC0XXX</h:BICFI>\n"
+ " </h:FinInstnId>\n"
+ " </h:FIId>\n"
+ " </h:Fr>\n"
+ " <h:To>\n"
+ " <h:FIId>\n"
+ " <h:FinInstnId>\n"
+ " <h:BICFI>FOOZUSC0XXX</h:BICFI>\n"
+ " </h:FinInstnId>\n"
+ " </h:FIId>\n"
+ " </h:To>\n"
+ " <h:BizMsgIdr>STRUCTUREDADDR</h:BizMsgIdr>\n"
+ " <h:MsgDefIdr>pacs.008.001.10</h:MsgDefIdr>\n"
+ " <h:CreDt>2024-03-27T20:45:56-03:00</h:CreDt>\n"
+ " <h:Prty>NORM</h:Prty>\n"
+ "</h:AppHdr>\n"
+ "<Doc:Document xmlns:Doc=\"urn:iso:std:iso:20022:tech:xsd:pacs.008.001.10\">\n"
+ "</Doc:Document>\n"
+ "</RequestPayload>";

// Create a new instance of MxPacs00800110 with AppHdr CreDtTm ZuluDateTime
MxPacs00800110 mxPacs00800110 = MxPacs00800110.parse(xml);

// After CreDtTm is parsed, it should be 2024-03-27T20:45:56Z
assertTrue(mxPacs00800110.message().contains("2024-03-27T20:45:56Z"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlSchemaType;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.datatype.XMLGregorianCalendar;

import com.prowidesoftware.swift.model.mx.adapters.IsoDateTimeAdapter;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
Expand Down Expand Up @@ -56,6 +59,7 @@ public class BusinessApplicationHeaderV01Impl {
protected String bizSvc;
@XmlElement(name = "CreDt", required = true)
@XmlSchemaType(name = "dateTime")
@XmlJavaTypeAdapter(IsoDateTimeAdapter.class)
protected XMLGregorianCalendar creDt;
@XmlElement(name = "CpyDplct")
@XmlSchemaType(name = "string")
Expand Down

0 comments on commit e05e4ad

Please sign in to comment.