From e05e4ade2f7961adc42e4c108c753224f29be82f Mon Sep 17 00:00:00 2001 From: Fernando de Sarriera <71713679+fernando-prowide@users.noreply.github.com> Date: Tue, 21 May 2024 19:17:57 +0200 Subject: [PATCH] PW-1875: ZuluTime format for BAH1 (#114) * 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 --- .gitignore | 1 + CHANGELOG.md | 6 +- .../swift/model/mx/BusinessAppHdrV01.java | 5 + .../mx/adapters/ZuluDateTimeAdapter.java | 102 +++++++++++ .../mx/adapters/ZuluDateTimeAdapterTest.java | 160 ++++++++++++++++++ .../dic/BusinessApplicationHeaderV01Impl.java | 4 + 6 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 iso20022-core/src/main/java/com/prowidesoftware/swift/model/mx/adapters/ZuluDateTimeAdapter.java create mode 100644 iso20022-core/src/test/java/com/prowidesoftware/swift/model/mx/adapters/ZuluDateTimeAdapterTest.java diff --git a/.gitignore b/.gitignore index 377f482c9..1f245ea09 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ lib release.sh .idea/sonarlint *.hprof +.idea/copilot diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b2b54384..b188ac6ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/iso20022-core/src/main/java/com/prowidesoftware/swift/model/mx/BusinessAppHdrV01.java b/iso20022-core/src/main/java/com/prowidesoftware/swift/model/mx/BusinessAppHdrV01.java index ae15aac9a..1e320e0b1 100644 --- a/iso20022-core/src/main/java/com/prowidesoftware/swift/model/mx/BusinessAppHdrV01.java +++ b/iso20022-core/src/main/java/com/prowidesoftware/swift/model/mx/BusinessAppHdrV01.java @@ -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; @@ -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 { @@ -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) { diff --git a/iso20022-core/src/main/java/com/prowidesoftware/swift/model/mx/adapters/ZuluDateTimeAdapter.java b/iso20022-core/src/main/java/com/prowidesoftware/swift/model/mx/adapters/ZuluDateTimeAdapter.java new file mode 100644 index 000000000..b4906d274 --- /dev/null +++ b/iso20022-core/src/main/java/com/prowidesoftware/swift/model/mx/adapters/ZuluDateTimeAdapter.java @@ -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. + *

+ * 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. + *

+ * 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 { + + private final SimpleDateFormat marshalFormat; + private final SimpleDateFormat unmarshalFormat; + private final XmlAdapter 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 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", ""); + } + } +} diff --git a/iso20022-core/src/test/java/com/prowidesoftware/swift/model/mx/adapters/ZuluDateTimeAdapterTest.java b/iso20022-core/src/test/java/com/prowidesoftware/swift/model/mx/adapters/ZuluDateTimeAdapterTest.java new file mode 100644 index 000000000..3566b838f --- /dev/null +++ b/iso20022-core/src/test/java/com/prowidesoftware/swift/model/mx/adapters/ZuluDateTimeAdapterTest.java @@ -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 = "\n" + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " FOOYVEC0XXX\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " FOOZUSC0XXX\n" + + " \n" + + " \n" + + " \n" + + " STRUCTUREDADDR\n" + + " pacs.008.001.10\n" + + " 2024-03-27T20:45:56Z\n" + + " NORM\n" + + "\n" + + "\n" + + "\n" + + ""; + + // 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 = "\n" + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " FOOYVEC0XXX\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " FOOZUSC0XXX\n" + + " \n" + + " \n" + + " \n" + + " STRUCTUREDADDR\n" + + " pacs.008.001.10\n" + + " 2024-03-27T20:45:56-03:00\n" + + " NORM\n" + + "\n" + + "\n" + + "\n" + + ""; + + // 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")); + } +} diff --git a/model-common-types/src/generated/java/com/prowidesoftware/swift/model/mx/dic/BusinessApplicationHeaderV01Impl.java b/model-common-types/src/generated/java/com/prowidesoftware/swift/model/mx/dic/BusinessApplicationHeaderV01Impl.java index 4c1b1190d..4286f6593 100644 --- a/model-common-types/src/generated/java/com/prowidesoftware/swift/model/mx/dic/BusinessApplicationHeaderV01Impl.java +++ b/model-common-types/src/generated/java/com/prowidesoftware/swift/model/mx/dic/BusinessApplicationHeaderV01Impl.java @@ -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; @@ -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")