diff --git a/java/code/src/com/redhat/rhn/frontend/xmlrpc/BaseHandler.java b/java/code/src/com/redhat/rhn/frontend/xmlrpc/BaseHandler.java index 701621e8fc3e..51ac7b4473f4 100644 --- a/java/code/src/com/redhat/rhn/frontend/xmlrpc/BaseHandler.java +++ b/java/code/src/com/redhat/rhn/frontend/xmlrpc/BaseHandler.java @@ -42,6 +42,9 @@ import com.suse.manager.api.ReadOnly; import com.suse.salt.netapi.utils.Xor; +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.hibernate.HibernateException; @@ -409,6 +412,37 @@ public static void ensureUserRole(User user, Role role) } } + /** + * Parse an input element uniformly for both XMLRPC and JSON APIs. + *

+ * Useful when parsing input parameter values inside complex structs where there's no type information available. + *

+ * XMLRPC and JSON APIs automatically parse the values for top-level parameters according to the type information + * available. However, the values nested inside a complex struct must be parsed inside the specific handler method. + * + * @param argIn the input value + * @return the parsed {@link T} object + * @throws InvalidParameterException when the input cannot be parsed + */ + protected static T parseInputValue(Object argIn, Class typeIn) throws InvalidParameterException { + T value; + try { + if (typeIn.isAssignableFrom(argIn.getClass())) { + // Assume exact type (XMLRPC) + value = typeIn.cast(argIn); + } + else { + // Interpret as string (JSON over HTTP) + value = new Gson().fromJson("\"" + argIn + "\"", typeIn); + } + } + catch (ClassCastException | JsonSyntaxException e) { + throw new InvalidParameterException("Wrong input format", e); + } + + return value; + } + /** * Ensure the org exists * @param orgId the org id to check diff --git a/java/code/src/com/redhat/rhn/frontend/xmlrpc/channel/software/ChannelSoftwareHandler.java b/java/code/src/com/redhat/rhn/frontend/xmlrpc/channel/software/ChannelSoftwareHandler.java index bb2d4003dc30..a677c162e1bd 100644 --- a/java/code/src/com/redhat/rhn/frontend/xmlrpc/channel/software/ChannelSoftwareHandler.java +++ b/java/code/src/com/redhat/rhn/frontend/xmlrpc/channel/software/ChannelSoftwareHandler.java @@ -1516,7 +1516,7 @@ public List listErrata(User loggedInUser, @ReadOnly public List listErrata(User loggedInUser, String channelLabel) throws NoSuchChannelException { - return listErrata(loggedInUser, channelLabel, (Date) null); + return listErrata(loggedInUser, channelLabel, null); } /** diff --git a/java/code/src/com/redhat/rhn/frontend/xmlrpc/errata/ErrataHandler.java b/java/code/src/com/redhat/rhn/frontend/xmlrpc/errata/ErrataHandler.java index 4a6852036160..763fba2562dc 100644 --- a/java/code/src/com/redhat/rhn/frontend/xmlrpc/errata/ErrataHandler.java +++ b/java/code/src/com/redhat/rhn/frontend/xmlrpc/errata/ErrataHandler.java @@ -279,37 +279,19 @@ public Integer setDetails(User loggedInUser, String advisoryName, Map details = new HashMap<>(); + Date expectedDate = Date.from(LocalDate.of(1989, 4, 1).atStartOfDay().toInstant(ZoneOffset.UTC)); + // Set using Date instance (XMLRPC) + details.put("issue_date", expectedDate); + // Set using ISO-8601 String (JSON over HTTP) + details.put("update_date", "1989-04-01T00:00:00Z"); + + int result = handler.setDetails(user, errata.getAdvisoryName(), details); + + assertEquals(1, result); + + Errata updatedErrata = ErrataManager.lookupErrata(errata.getId(), user); + assertEquals(expectedDate, updatedErrata.getIssueDate()); + assertEquals(expectedDate, updatedErrata.getUpdateDate()); + } + @Test public void testListAffectedSystems() throws Exception { //no affected systems diff --git a/java/code/src/com/redhat/rhn/frontend/xmlrpc/system/provisioning/snapshot/SnapshotHandler.java b/java/code/src/com/redhat/rhn/frontend/xmlrpc/system/provisioning/snapshot/SnapshotHandler.java index 487f9707159d..336e0e27306b 100644 --- a/java/code/src/com/redhat/rhn/frontend/xmlrpc/system/provisioning/snapshot/SnapshotHandler.java +++ b/java/code/src/com/redhat/rhn/frontend/xmlrpc/system/provisioning/snapshot/SnapshotHandler.java @@ -290,10 +290,10 @@ public int deleteSnapshots(User loggedInUser, Map dateDetails) { Date endDate = null; if (dateDetails.containsKey("startDate")) { - startDate = (Date)dateDetails.get("startDate"); + startDate = parseInputValue(dateDetails.get("startDate"), Date.class); } if (dateDetails.containsKey("endDate")) { - endDate = (Date)dateDetails.get("endDate"); + endDate = parseInputValue(dateDetails.get("endDate"), Date.class); } return deleteSnapshots(loggedInUser, startDate, endDate); } diff --git a/java/code/src/com/suse/manager/api/ApiRequestParser.java b/java/code/src/com/suse/manager/api/ApiRequestParser.java index 867680eba96f..dbb29102824a 100644 --- a/java/code/src/com/suse/manager/api/ApiRequestParser.java +++ b/java/code/src/com/suse/manager/api/ApiRequestParser.java @@ -31,7 +31,6 @@ */ public class ApiRequestParser { private final Gson gson; - private final JsonParser parser = new JsonParser(); /** * Constructs a parser with a {@link Gson} instance @@ -49,7 +48,7 @@ public ApiRequestParser(Gson gsonIn) { * @return the JSON object properties as key-value pairs */ public Map parseBody(String body) throws ParseException { - JsonElement bodyElement = parser.parse(body); + JsonElement bodyElement = JsonParser.parseString(body); if (bodyElement.isJsonNull()) { return Collections.emptyMap(); @@ -115,11 +114,11 @@ private JsonElement parseQueryParam(String value) throws ParseException { JsonElement element; try { // Try to parse the literal value - element = parser.parse(value); + element = JsonParser.parseString(value); } catch (JsonSyntaxException e) { // Invalid syntax, try as string - element = parser.parse('"' + value + '"'); + element = JsonParser.parseString('"' + value + '"'); } if (element.isJsonPrimitive() || element.isJsonNull()) { diff --git a/java/code/src/com/suse/manager/api/ListDeserializer.java b/java/code/src/com/suse/manager/api/ListDeserializer.java index f3c89d64b26f..a8797132e628 100644 --- a/java/code/src/com/suse/manager/api/ListDeserializer.java +++ b/java/code/src/com/suse/manager/api/ListDeserializer.java @@ -28,9 +28,7 @@ /** * Custom {@link List} deserializer that handles arbitrary numbers properly - * @deprecated the same behavior can be achieved using ToNumberPolicy.LONG_OR_DOUBLE with gson-2.8.9 */ -@Deprecated public class ListDeserializer implements JsonDeserializer> { @Override public List deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) diff --git a/java/code/src/com/suse/manager/api/MapDeserializer.java b/java/code/src/com/suse/manager/api/MapDeserializer.java index 9d3fcabe60e7..7467ace5df7d 100644 --- a/java/code/src/com/suse/manager/api/MapDeserializer.java +++ b/java/code/src/com/suse/manager/api/MapDeserializer.java @@ -28,9 +28,7 @@ /** * Custom {@link Map} deserializer that handles arbitrary numbers properly - * @deprecated the same behavior can be achieved using ToNumberPolicy.LONG_OR_DOUBLE with gson-2.8.9 */ -@Deprecated public class MapDeserializer implements JsonDeserializer> { @Override public Map deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) diff --git a/java/code/src/com/suse/manager/api/test/RouteFactoryTest.java b/java/code/src/com/suse/manager/api/test/RouteFactoryTest.java index 7a642bfccc1e..b5baec1f3bbd 100644 --- a/java/code/src/com/suse/manager/api/test/RouteFactoryTest.java +++ b/java/code/src/com/suse/manager/api/test/RouteFactoryTest.java @@ -67,7 +67,6 @@ public class RouteFactoryTest extends BaseControllerTestCase { .registerTypeAdapter(Map.class, new MapDeserializer()) .registerTypeAdapter(List.class, new ListDeserializer()) .create(); - private final JsonParser parser = new JsonParser(); private RouteFactory routeFactory; private TestHandler handler; @@ -718,7 +717,7 @@ public void testQueryStringArray() throws Exception { * @return the result object */ private T getResult(String response, Type resultType) { - JsonObject obj = parser.parse(response).getAsJsonObject(); + JsonObject obj = JsonParser.parseString(response).getAsJsonObject(); boolean isSuccess = obj.getAsJsonPrimitive("success").getAsBoolean(); assertTrue(isSuccess); diff --git a/java/spacewalk-java.changes.cbbayburt.api-datetime-input b/java/spacewalk-java.changes.cbbayburt.api-datetime-input new file mode 100644 index 000000000000..46797ebdef15 --- /dev/null +++ b/java/spacewalk-java.changes.cbbayburt.api-datetime-input @@ -0,0 +1 @@ +- Fix date input in 'errata.setDetails' endpoint in the HTTP API