Skip to content

Commit 6d1bf09

Browse files
committed
Initial migration away from JAXB annotations
Since the underlying serializer relies on Gson lib, we are unable to use directly Jackson annotations since they don't work as expected. This was verified via test coverage. Furthermore, the `required=true` validation was never enforced (see AbortRequestTest tests)
1 parent 3ec987a commit 6d1bf09

7 files changed

Lines changed: 87 additions & 54 deletions

File tree

src/main/java/com/adyen/model/nexo/AbortRequest.java

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package com.adyen.model.nexo;
22

3+
import com.google.gson.annotations.SerializedName;
34
import io.swagger.v3.oas.annotations.media.Schema;
4-
import javax.xml.bind.annotation.XmlAccessType;
5-
import javax.xml.bind.annotation.XmlAccessorType;
6-
import javax.xml.bind.annotation.XmlElement;
7-
import javax.xml.bind.annotation.XmlType;
85

96
/**
107
* Definition: Body of the Abort Request messageType. -- Usage: It conveys Information requested for
@@ -29,24 +26,20 @@
2926
* </complexType>
3027
* </pre>
3128
*/
32-
@XmlAccessorType(XmlAccessType.FIELD)
33-
@XmlType(
34-
name = "AbortRequest",
35-
propOrder = {"messageReference", "abortReason", "displayOutput"})
3629
public class AbortRequest {
3730

3831
/** The Message reference. */
39-
@XmlElement(name = "MessageReference", required = true)
32+
@SerializedName("MessageReference")
4033
@Schema(description = "Identification of a previous POI transaction.")
4134
protected MessageReference messageReference;
4235

4336
/** The Abort reason. */
44-
@XmlElement(name = "AbortReason", required = true)
37+
@SerializedName("AbortReason")
4538
@Schema(description = "Reason of aborting a transaction")
4639
protected String abortReason;
4740

4841
/** The Display output. */
49-
@XmlElement(name = "DisplayOutput")
42+
@SerializedName("DisplayOutput")
5043
@Schema(
5144
description =
5245
"Information to display and the way to process the display. --Rule: To display an abort message to the Customer")

src/main/java/com/adyen/model/nexo/DisplayRequest.java

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
package com.adyen.model.nexo;
22

3+
import com.google.gson.annotations.SerializedName;
34
import io.swagger.v3.oas.annotations.media.Schema;
45
import java.util.ArrayList;
56
import java.util.List;
6-
import javax.xml.bind.annotation.XmlAccessType;
7-
import javax.xml.bind.annotation.XmlAccessorType;
8-
import javax.xml.bind.annotation.XmlElement;
9-
import javax.xml.bind.annotation.XmlType;
107

118
/**
129
* Definition: Content of the Display Request messageType. -- Usage: It conveys the data to display
@@ -29,14 +26,10 @@
2926
* &lt;/complexType&gt;
3027
* </pre>
3128
*/
32-
@XmlAccessorType(XmlAccessType.FIELD)
33-
@XmlType(
34-
name = "DisplayRequest",
35-
propOrder = {"displayOutput"})
3629
public class DisplayRequest {
3730

3831
/** The Display output. */
39-
@XmlElement(name = "DisplayOutput", required = true)
32+
@SerializedName("DisplayOutput")
4033
@Schema(
4134
description =
4235
"Information to display and the way to process the display. --Rule: Complete display content for output devices. At most one DisplayOutput per Device/ InfoQualify pair")

src/main/java/com/adyen/model/nexo/LoginRequest.java

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
package com.adyen.model.nexo;
22

3+
import com.google.gson.annotations.SerializedName;
34
import io.swagger.v3.oas.annotations.media.Schema;
45
import java.util.ArrayList;
56
import java.util.List;
6-
import javax.xml.bind.annotation.XmlAccessType;
7-
import javax.xml.bind.annotation.XmlAccessorType;
8-
import javax.xml.bind.annotation.XmlElement;
9-
import javax.xml.bind.annotation.XmlSchemaType;
10-
import javax.xml.bind.annotation.XmlType;
117
import javax.xml.datatype.XMLGregorianCalendar;
128

139
/**
@@ -39,74 +35,69 @@
3935
* &lt;/complexType&gt;
4036
* </pre>
4137
*/
42-
@XmlAccessorType(XmlAccessType.FIELD)
43-
@XmlType(
44-
name = "LoginRequest",
45-
propOrder = {"dateTime", "saleSoftware", "saleTerminalData"})
4638
public class LoginRequest {
4739

4840
/** The Date time. */
49-
@XmlElement(name = "DateTime", required = true)
41+
@SerializedName("DateTime")
5042
@Schema(description = "Date and Time")
51-
@XmlSchemaType(name = "dateTime")
5243
protected XMLGregorianCalendar dateTime;
5344

5445
/** The Sale software. */
55-
@XmlElement(name = "SaleSoftware", required = true)
46+
@SerializedName("SaleSoftware")
5647
@Schema(
5748
description =
5849
"Information related to the software of the Sale System which manages the Sale to POI protocol.")
5950
protected SaleSoftware saleSoftware;
6051

6152
/** The Sale terminal data. */
62-
@XmlElement(name = "SaleTerminalData")
53+
@SerializedName("SaleTerminalData")
6354
@Schema(
6455
description =
6556
"Information related to the software and hardware feature of the Sale Terminal. --Rule: Present if the login involve a Sale Terminal")
6657
protected SaleTerminalData saleTerminalData;
6758

6859
/** The Training mode flag. */
69-
@XmlElement(name = "TrainingModeFlag")
60+
@SerializedName("TrainingModeFlag")
7061
@Schema(
7162
description =
7263
"Training mode --Rule: The POI does not realise the transaction with the Acquirer")
7364
protected Boolean trainingModeFlag;
7465

7566
/** The Operator language. */
76-
@XmlElement(name = "OperatorLanguage", required = true)
67+
@SerializedName("OperatorLanguage")
7768
@Schema(
7869
description =
7970
"Language of the Cashier or Operator. --Rule: Default value for Device type displays")
8071
protected String operatorLanguage;
8172

8273
/** The Operator id. */
83-
@XmlElement(name = "OperatorID")
74+
@SerializedName("OperatorID")
8475
@Schema(
8576
description =
8677
"Identification of the Cashier or Operator. --Rule: 4 conditions to send it: a) the Sale System wants the POI log it in the transaction log b) because of reconciliation")
8778
protected String operatorID;
8879

8980
/** The Shift number. */
90-
@XmlElement(name = "ShiftNumber")
81+
@SerializedName("ShiftNumber")
9182
@Schema(description = "Shift number. --Rule: Same as OperatorID")
9283
protected String shiftNumber;
9384

9485
/** The Token requested. */
95-
@XmlElement(name = "TokenRequestedType")
86+
@SerializedName("TokenRequestedType")
9687
@Schema(
9788
description =
9889
"Type of token replacing the PAN of a payment card to identify the payment mean of the customer. --Rule: If a token is requested during the the session.")
9990
protected TokenRequestedType tokenRequestedType;
10091

10192
/** The Customer order req. */
102-
@XmlElement(name = "CustomerOrderReq")
93+
@SerializedName("CustomerOrderReq")
10394
@Schema(
10495
description =
10596
"List of customer orders must be sent in response message. --Rule: If customer orders must be listed in Card Acquisition and Payment response messages during the session.")
10697
protected List<CustomerOrderReqType> customerOrderReq;
10798

10899
/** The Poi serial number. */
109-
@XmlElement(name = "POISerialNumber")
100+
@SerializedName("POISerialNumber")
110101
@Schema(
111102
description =
112103
"Serial number of a POI Terminal --Rule: If the login involve a POI Terminal and not the first Login to the POI System")

src/main/java/com/adyen/model/nexo/MessageHeader.java

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package com.adyen.model.nexo;
22

3+
import com.google.gson.annotations.SerializedName;
34
import io.swagger.v3.oas.annotations.media.Schema;
4-
import javax.xml.bind.annotation.XmlAccessType;
5-
import javax.xml.bind.annotation.XmlAccessorType;
6-
import javax.xml.bind.annotation.XmlElement;
7-
import javax.xml.bind.annotation.XmlType;
85

96
/**
107
* Definition: Message header of the Sale to POI protocol messageType. -- Usage: It conveys
@@ -31,32 +28,30 @@
3128
* &lt;/complexType&gt;
3229
* </pre>
3330
*/
34-
@XmlAccessorType(XmlAccessType.FIELD)
35-
@XmlType(name = "MessageHeader")
3631
public class MessageHeader {
3732

3833
/** The Protocol version. */
39-
@XmlElement(name = "ProtocolVersion")
34+
@SerializedName("ProtocolVersion")
4035
@Schema(description = ">--Rule:If MessageCategory is Login or Diagnosis")
4136
protected String protocolVersion;
4237

4338
/** The Message class. */
44-
@XmlElement(name = "MessageClass", required = true)
39+
@SerializedName("MessageClass")
4540
@Schema(description = "Class of the message")
4641
protected MessageClassType messageClass;
4742

4843
/** The Message category. */
49-
@XmlElement(name = "MessageCategory", required = true)
44+
@SerializedName("MessageCategory")
5045
@Schema(description = "Category of message.")
5146
protected MessageCategoryType messageCategory;
5247

5348
/** The Message type. */
54-
@XmlElement(name = "MessageType", required = true)
49+
@SerializedName("MessageType")
5550
@Schema(description = "Type of message of the Sale to POI protocol")
5651
protected MessageType messageType;
5752

5853
/** The Service id. */
59-
@XmlElement(name = "ServiceID")
54+
@SerializedName("ServiceID")
6055
@Schema(
6156
description =
6257
"Identification of a message pair, which processes a transaction --Rule: If \"Service\" or \"Event\" MessageClass message. If \"Device\" MessageClass, and request from POI or response from Sale.",
@@ -65,20 +60,20 @@ public class MessageHeader {
6560
protected String serviceID;
6661

6762
/** The Device id. */
68-
@XmlElement(name = "DeviceID")
63+
@SerializedName("DeviceID")
6964
@Schema(
7065
description = "Identification of a device message pair --Rule: If \"Device\" MessageClass")
7166
protected String deviceID;
7267

7368
/** The Sale id. */
74-
@XmlElement(name = "SaleID", required = true)
69+
@SerializedName("SaleID")
7570
@Schema(
7671
description =
7772
"Identification of a Sale System or a Sale Terminal for the Sale to POI protocol")
7873
protected String saleID;
7974

8075
/** The Poiid. */
81-
@XmlElement(name = "POIID", required = true)
76+
@SerializedName("POIID")
8277
@Schema(
8378
description = "Identification of a POI System or a POI Terminal for the Sale to POI protocol")
8479
protected String poiid;

src/test/java/com/adyen/model/nexo/AbortRequestTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.adyen.model.nexo;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
5+
import static org.junit.jupiter.api.Assertions.assertNull;
46

57
import com.adyen.terminal.serialization.TerminalAPIGsonBuilder;
68
import com.google.gson.Gson;
@@ -66,4 +68,48 @@ public void testShouldSerializeAndDeserializeFromMockFile() throws IOException {
6668
deserializedAbortRequest.getDisplayOutput().getInfoQualify(),
6769
roundTripAbortRequest.getDisplayOutput().getInfoQualify());
6870
}
71+
72+
@Test
73+
public void testDeserializationWithMissingRequiredFields() throws IOException {
74+
Gson terminalApiGson = TerminalAPIGsonBuilder.create();
75+
76+
// JSON missing MessageReference (required field)
77+
String jsonMissingMessageReference =
78+
NexoTestUtils.readResource(
79+
"mocks/terminal-api/abort-request-missing-message-reference.json");
80+
81+
AbortRequest result1 =
82+
terminalApiGson.fromJson(jsonMissingMessageReference, AbortRequest.class);
83+
assertNotNull(result1, "Deserialization should succeed even with missing required field");
84+
assertNull(
85+
result1.getMessageReference(), "MessageReference should be null when missing from JSON");
86+
assertNotNull(result1.getAbortReason(), "AbortReason should be present");
87+
88+
// JSON missing AbortReason (required field)
89+
String jsonMissingAbortReason =
90+
NexoTestUtils.readResource("mocks/terminal-api/abort-request-missing-abort-reason.json");
91+
92+
AbortRequest result2 = terminalApiGson.fromJson(jsonMissingAbortReason, AbortRequest.class);
93+
assertNotNull(result2, "Deserialization should succeed even with missing required field");
94+
assertNotNull(result2.getMessageReference(), "MessageReference should be present");
95+
assertNull(result2.getAbortReason(), "AbortReason should be null when missing from JSON");
96+
}
97+
98+
@Test
99+
public void testSerializationWithNullRequiredFields() {
100+
Gson terminalApiGson = TerminalAPIGsonBuilder.create();
101+
102+
AbortRequest request = new AbortRequest();
103+
request.setAbortReason("cancelled-by-shopper");
104+
// Not setting MessageReference (required field)
105+
106+
String serialized = terminalApiGson.toJson(request);
107+
assertNotNull(serialized, "Serialization should succeed even with null required field");
108+
109+
// Verify it can be deserialized back
110+
AbortRequest deserialized = terminalApiGson.fromJson(serialized, AbortRequest.class);
111+
assertNotNull(deserialized);
112+
assertNull(
113+
deserialized.getMessageReference(), "MessageReference should remain null after round-trip");
114+
}
69115
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"MessageReference": {
3+
"MessageCategory": "Payment",
4+
"ServiceID": "service-id",
5+
"SaleID": "sale-id",
6+
"POIID": "poi-id"
7+
}
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"AbortReason": "cancelled-by-shopper",
3+
"DisplayOutput": {
4+
"Device": "CustomerDisplay",
5+
"InfoQualify": "Error"
6+
}
7+
}

0 commit comments

Comments
 (0)