Skip to content

Commit

Permalink
GH-118 - CU-86b15d33f ⁃ retro compatibility with dates between v9 and…
Browse files Browse the repository at this point in the history
… v10 (#129)

* CU-86b15d33f_GH-118-⁃-Retro-compatibility-with-dates-between-v9-and-v10

* changelog

---------

Co-authored-by: zubri <[email protected]>
  • Loading branch information
ptorres-prowide and zubri authored Oct 31, 2024
1 parent cf5c986 commit 056f686
Show file tree
Hide file tree
Showing 6 changed files with 708 additions and 9 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Prowide ISO 20022 - CHANGELOG

#### 10.1.8 - SNAPSHOT
* (GH-118) Added `toJsonV9` and `fromJsonV9` in the `AbstractMX` to handle retro-compatibility with Java 8 DateTime elements in JSON serialization

#### 10.1.7 - August 2024
* (PW-1958) Fixed the `DefaultMxMetadataStrategy` NPE issue when the amount values are null

Expand Down
3 changes: 1 addition & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -345,9 +345,8 @@ dependencies {
//implementation 'com.google.code.gson:gson:2.9.0'

// jaxb is required because it is no longer included in the JDK
implementation 'com.sun.xml.bind:jaxb-impl:4.0.5'
// bind-api is included transitively by jaxb-impl
// implementation 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.2'
implementation 'com.sun.xml.bind:jaxb-impl:4.0.5'
}

project.ext {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.prowidesoftware.swift.model.MxId;
import com.prowidesoftware.swift.model.mt.AbstractMT;
import com.prowidesoftware.swift.model.mx.adapters.*;
import com.prowidesoftware.swift.model.mx.adapters.v9.V9DateTimeJsonAdapter;
import com.prowidesoftware.swift.utils.Lib;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.annotation.XmlTransient;
Expand All @@ -37,6 +38,7 @@
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.stream.StreamSource;
Expand Down Expand Up @@ -260,7 +262,7 @@ public static AbstractMX parse(final String xml, MxId id, final MxReadConfigurat
* @since 7.10.3
*/
protected static <T> T fromJson(String json, Class<T> classOfT) {
final Gson gson = getGsonBuilderWithCustomAdapters();
final Gson gson = getGsonBuilderWithV10Adapters();
return gson.fromJson(json, classOfT);
}

Expand All @@ -272,7 +274,41 @@ protected static <T> T fromJson(String json, Class<T> classOfT) {
* @since 7.10.3
*/
public static AbstractMX fromJson(String json) {
final Gson gson = getGsonBuilderWithCustomAdapters();
final Gson gson = getGsonBuilderWithV10Adapters();
return gson.fromJson(json, AbstractMX.class);
}

/**
* Deserializes the given JSON string into an object of the specified class, using version 9 (Java 8) adapters.
*
* <p>This method ensures compatibility by checking for date-time fields stored in JSON as
* {@link XMLGregorianCalendar} based json elements and converting them into {@link OffsetDateTime}. The Gson
* adapters (version 9) manage both Java 8 and Java 11 formats.
*
* @param <T> the type of the object to be deserialized
* @param json a JSON representation of the object
* @param classOfT the class of the object to be deserialized
* @return a deserialized instance of the specified class, or null if the JSON string cannot be parsed.
* @since 10.1.8
*/
protected static <T> T fromJsonV9(String json, Class<T> classOfT) {
final Gson gson = getGsonBuilderWithV9Adapters();
return gson.fromJson(json, classOfT);
}

/**
* Deserializes the given JSON string into a specific MX message object, using version 9 (Java 8) adapters.
*
* <p>This method ensures compatibility with older versions by converting
* {@link XMLGregorianCalendar} based json elements in JSON to {@link OffsetDateTime} if needed. The
* deserialization uses custom adapters (version 9) that handle both formats for date-time fields.
*
* @param json a JSON representation of an MX message
* @return a deserialized instance of {@link AbstractMX}, or null if the JSON string cannot be parsed.
* @since 10.1.8
*/
public static AbstractMX fromJsonV9(String json) {
final Gson gson = getGsonBuilderWithV9Adapters();
return gson.fromJson(json, AbstractMX.class);
}

Expand Down Expand Up @@ -628,19 +664,45 @@ public Element element(JAXBContext inputContext) {
@Override
public String toJson() {
// we use AbstractMX and not this.getClass() in order to force usage of the adapter
final Gson gson = getGsonBuilderWithCustomAdapters();
final Gson gson = getGsonBuilderWithV10Adapters();
return gson.toJson(this, AbstractMX.class);
}

private static Gson getGsonBuilderWithCustomAdapters() {
final Gson gson = new GsonBuilder()
/**
* Serializes this MX message into its JSON representation, using version 9 (Java 8) adapters.
*
* <p>This method ensures that when serializing to JSON, {@link OffsetDateTime} fields can be converted
* back into {@link XMLGregorianCalendar} based json element format for compatibility with older systems that still use the Java 8 format.
*
* @return a JSON representation of the MX message, compatible with Java 8
* @since 10.1.8
*/
public String toJsonV9() {
final Gson gson = getGsonBuilderWithV9Adapters();
return gson.toJson(this, AbstractMX.class);
}

private static GsonBuilder getGsonBuilderWithCustomAdapters() {
return new GsonBuilder()
.registerTypeAdapter(AbstractMX.class, new AbstractMXAdapter())
.registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeJsonAdapter())
.registerTypeAdapter(OffsetTime.class, new OffsetTimeJsonAdapter())
.registerTypeAdapter(LocalDate.class, new LocalDateJsonAdapter())
.registerTypeAdapter(Year.class, new YearJsonAdapter())
.registerTypeAdapter(YearMonth.class, new YearMonthJsonAdapter())
.registerTypeAdapter(AppHdr.class, new AppHdrAdapter())
.registerTypeAdapter(AppHdr.class, new AppHdrAdapter());
}

private static Gson getGsonBuilderWithV9Adapters() {
final Gson gson = getGsonBuilderWithCustomAdapters()
.registerTypeAdapter(OffsetDateTime.class, new V9DateTimeJsonAdapter())
.setPrettyPrinting()
.create();
return gson;
}

private static Gson getGsonBuilderWithV10Adapters() {
final Gson gson = getGsonBuilderWithCustomAdapters()
.registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeJsonAdapter())
.setPrettyPrinting()
.create();
return gson;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package com.prowidesoftware.swift.model.mx.adapters.v9;

import com.google.gson.*;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Adapter for serializing and deserializing {@link OffsetDateTime} to and from JSON in a format compatible
* with the legacy {@code XMLGregorianCalendar} used in older versions of the library.
* <p>
* This adapter is used to maintain backward compatibility with systems that expect the older
* date-time format while allowing migration to Java 11's {@code OffsetDateTime}.
* </p>
*
* <p><b>Serialization</b></p>
* The {@code serialize} method converts an {@code OffsetDateTime} object into a JSON structure
* similar to the structure used by {@code XMLGregorianCalendar}, preserving the fields for year,
* month, day, time, timezone, and fractional seconds if present.
*
* <p><b>Deserialization</b></p>
* The {@code deserialize} method converts a JSON structure formatted for {@code XMLGregorianCalendar}
* into an {@code OffsetDateTime} object, parsing the legacy fields and converting them to their modern
* equivalents, including handling timezone and fractional seconds.
*
* @since 10.1.8
*/
public class V9DateTimeJsonAdapter implements JsonSerializer<OffsetDateTime>, JsonDeserializer<OffsetDateTime> {
private static final Logger log = Logger.getLogger(V9DateTimeJsonAdapter.class.getName());

private final Gson gson = new Gson();

/**
* Serializes an {@link OffsetDateTime} into JSON format compatible with the older
* {@code XMLGregorianCalendar} representation.
*
* @param offsetDateTime the {@code OffsetDateTime} to be serialized
* @param type the specific genericized type of the source object
* @param jsonSerializationContext the context for serialization
* @return a {@link JsonElement} representing the serialized date-time information
*/
@Override
public JsonElement serialize(
OffsetDateTime offsetDateTime, Type type, JsonSerializationContext jsonSerializationContext) {

XMLGregorianCalendarDTO xMLGregorianCalendarDTO = new XMLGregorianCalendarDTO();

xMLGregorianCalendarDTO.year = offsetDateTime.getYear();
xMLGregorianCalendarDTO.month = offsetDateTime.getMonthValue();
xMLGregorianCalendarDTO.day = offsetDateTime.getDayOfMonth();
xMLGregorianCalendarDTO.hour = offsetDateTime.getHour();
xMLGregorianCalendarDTO.minute = offsetDateTime.getMinute();
xMLGregorianCalendarDTO.second = offsetDateTime.getSecond();
if (offsetDateTime.getNano() != 0) {
xMLGregorianCalendarDTO.fractionalSecond = BigDecimal.valueOf(offsetDateTime.getNano())
.divide(BigDecimal.valueOf(1_000_000_000), 3, RoundingMode.DOWN);
}
if (offsetDateTime.getOffset() != null) {
xMLGregorianCalendarDTO.timezone = offsetDateTime.getOffset().getTotalSeconds() / 60;
}

return gson.toJsonTree(xMLGregorianCalendarDTO, XMLGregorianCalendarDTO.class);
}

/**
* Deserializes a JSON element into an {@link OffsetDateTime}, converting the legacy
* {@code XMLGregorianCalendar} fields into their modern equivalents.
*
* @param xMLGregorianCalendarJsonElement the JSON data to be deserialized
* @param type the specific genericized type of the source object
* @param jsonDeserializationContext the context for deserialization
* @return the deserialized {@code OffsetDateTime}, or {@code null} if parsing fails
*/
@Override
public OffsetDateTime deserialize(
JsonElement xMLGregorianCalendarJsonElement,
Type type,
JsonDeserializationContext jsonDeserializationContext) {
try {
XMLGregorianCalendarDTO xMLGregorianCalendarDTO =
gson.fromJson(xMLGregorianCalendarJsonElement, XMLGregorianCalendarDTO.class);

// Prepare OffsetDateTime
ZoneOffset zoneoffset = ZoneOffset.ofTotalSeconds(xMLGregorianCalendarDTO.timezone * 60);
return OffsetDateTime.of(
xMLGregorianCalendarDTO.year,
xMLGregorianCalendarDTO.month,
xMLGregorianCalendarDTO.day,
xMLGregorianCalendarDTO.hour,
xMLGregorianCalendarDTO.minute,
xMLGregorianCalendarDTO.second,
(int) (xMLGregorianCalendarDTO.fractionalSecond.doubleValue() * 1000_000_000),
zoneoffset);
} catch (Exception e) {
log.log(Level.FINEST, "Cannot parse JSON into OffsetDateTime: " + e.getMessage(), e);
return null;
}
}

static class XMLGregorianCalendarDTO {
Integer year = 0;
Integer month = 0;
Integer day = 0;
Integer timezone = 0;
Integer hour = 0;
Integer minute = 0;
Integer second = 0;
BigDecimal fractionalSecond = BigDecimal.ZERO;
}
}
Loading

0 comments on commit 056f686

Please sign in to comment.