diff --git a/kie-server-parent/kie-server-api/src/main/java/org/kie/server/api/KieServerConstants.java b/kie-server-parent/kie-server-api/src/main/java/org/kie/server/api/KieServerConstants.java index 692061fb76..a9552329ad 100644 --- a/kie-server-parent/kie-server-api/src/main/java/org/kie/server/api/KieServerConstants.java +++ b/kie-server-parent/kie-server-api/src/main/java/org/kie/server/api/KieServerConstants.java @@ -63,6 +63,7 @@ public class KieServerConstants { public static final String KIE_SERVER_MODE = "org.kie.server.mode"; public static final String KIE_SERVER_INCLUDE_STACKTRACE = "org.kie.server.stacktrace.included"; public static final String KIE_SERVER_STRICT_ID_FORMAT = "org.kie.server.strict.id.format"; + public static final String JSON_HANDLE_XML_ANY_ELEMENTS_NAMES = "org.kie.server.strict.json.xmlanyelements"; public static final String KIE_SERVER_STRICT_JAVABEANS_SERIALIZERS = "org.kie.server.strict.javaBeans.serializers"; public static final String KIE_SERVER_STRICT_JAXB_FORMAT = "org.kie.server.strict.jaxb.format"; public static final String KIE_SERVER_IMAGESERVICE_MAX_NODES = "org.kie.server.service.image.max_nodes"; diff --git a/kie-server-parent/kie-server-api/src/main/java/org/kie/server/api/marshalling/json/JSONMarshaller.java b/kie-server-parent/kie-server-api/src/main/java/org/kie/server/api/marshalling/json/JSONMarshaller.java index 310139615c..2c058f34f1 100644 --- a/kie-server-parent/kie-server-api/src/main/java/org/kie/server/api/marshalling/json/JSONMarshaller.java +++ b/kie-server-parent/kie-server-api/src/main/java/org/kie/server/api/marshalling/json/JSONMarshaller.java @@ -39,6 +39,8 @@ import java.util.regex.Pattern; import javax.xml.bind.annotation.XmlAnyElement; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElements; import javax.xml.bind.annotation.adapters.XmlAdapter; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters; @@ -99,15 +101,15 @@ public class JSONMarshaller implements Marshaller { private static final Logger logger = LoggerFactory.getLogger(JSONMarshaller.class); private static final boolean STRICT_ID_FORMAT = Boolean.getBoolean(KieServerConstants.KIE_SERVER_STRICT_ID_FORMAT); + private boolean jsonHandleXmlAnyElementsNames = Boolean.getBoolean(KieServerConstants.JSON_HANDLE_XML_ANY_ELEMENTS_NAMES); private final boolean STRICT_JAVABEANS_SERIALIZERS = Boolean.getBoolean(KieServerConstants.KIE_SERVER_STRICT_JAVABEANS_SERIALIZERS); - private static final String FIELDS = "fields"; - private static final String NOT_NULL = "not_null"; + public static final String FIELDS = "fields"; + public static final String NOT_NULL = "not_null"; private boolean formatDate; private String dateFormatStr = System.getProperty("org.kie.server.json.date_format", "yyyy-MM-dd'T'hh:mm:ss.SSSZ"); - private boolean useStrictJavaBeans; private boolean fallbackClassLoaderEnabled = Boolean.parseBoolean(System.getProperty("org.kie.server.json.fallbackClassLoader.enabled", "false")); @@ -129,7 +131,7 @@ public static class JSONContext { private boolean stripped; private boolean wrap; - + private boolean writeNull; public JSONContext() { @@ -182,8 +184,6 @@ public void setWriteNull(boolean writeNull) { // Optional Marshaller Extension to handle new types private static final List EXTENSIONS; - - // Load Marshaller Extension static { logger.info("Marshaller extensions init"); @@ -224,7 +224,7 @@ private static CNFEBehavior getCNFEBehavior() { return CNFEBehavior.valueOf(cnfeBehaviorValue); } catch (IllegalArgumentException iae) { throw new MarshallingException(cnfeBehaviorValue + " is not supported for " + KieServerConstants.JSON_CUSTOM_OBJECT_DESERIALIZER_CNFE_BEHAVIOR + - ". Please choose from " + Arrays.asList(CNFEBehavior.values()).toString(), iae); + ". Please choose from " + Arrays.asList(CNFEBehavior.values()).toString(), iae); } } @@ -301,6 +301,7 @@ public boolean useForType(JavaType t) { } return false; } + }; typer = typer.init(JsonTypeInfo.Id.CLASS, null); typer = typer.inclusion(JsonTypeInfo.As.WRAPPER_OBJECT); @@ -403,8 +404,7 @@ public String marshall(Object input, Map parameters) { if (parameters.containsKey(MARSHALLER_PARAMETER_STRICT)) { jsonContext.get().setWrap(Boolean.parseBoolean((String) parameters.get(MARSHALLER_PARAMETER_STRICT))); } - if (NOT_NULL.equals(parameters.get(FIELDS))) - { + if (NOT_NULL.equals(parameters.get(FIELDS))) { jsonContext.get().setWriteNull(false); } return marshall(input); @@ -416,7 +416,7 @@ public String marshall(Object input, Map parameters) { @Override public String marshall(Object objectInput) { try { - return getMapper(objectMapper,notNullObjectMapper).writeValueAsString(wrap(objectInput)); + return getMapper(objectMapper, notNullObjectMapper).writeValueAsString(wrap(objectInput)); } catch (IOException e) { throw new MarshallingException("Error marshalling input", e); } @@ -432,7 +432,6 @@ public byte[] marshallAsBytes(Object objectInput) { } - @Override public T unmarshall(String serializedInput, Class type) { @@ -503,20 +502,34 @@ public ExtendedJaxbAnnotationIntrospector(List customClasses, ObjectM @Override public List findSubtypes(Annotated a) { - List base = super.findSubtypes(a); - List complete = new ArrayList(); - if (base != null) { - complete.addAll(base); - } if (customClasses != null) { - for (NamedType namedType : customClasses) { + for (NamedType namedType : customClasses) { Class clazz = namedType.getType(); if (!a.getRawType().equals(clazz) && a.getRawType().isAssignableFrom(clazz)) { complete.add(namedType); } } } + + XmlElements elements = findAnnotation(XmlElements.class, a, false, false, false); + if (elements != null) { + for (XmlElement elem : elements.value()) { + if (!jsonHandleXmlAnyElementsNames && customClasses.contains(new NamedType(elem.type(), elem.type().getSimpleName()))) { + continue; + } + String name = elem.name(); + if (MARKER_FOR_DEFAULT.equals(name)) { + name = null; + } + complete.add(new NamedType(elem.type(), name)); + } + } else { + List base = super.findSubtypes(a); + if (base != null) { + complete.addAll(base); + } + } return complete; } @@ -706,7 +719,7 @@ public CustomObjectSerializer(ObjectMapper customObjectMapper) { @Override public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { - jgen.writeRawValue(getMapper(customObjectMapper,notNullObjectMapper).writeValueAsString(value)); + jgen.writeRawValue(getMapper(customObjectMapper, notNullObjectMapper).writeValueAsString(value)); } } @@ -725,17 +738,17 @@ public void serialize(Object value, JsonGenerator jgen, SerializerProvider provi String className = value.getClass().getName(); if (value instanceof Collection) { - String collectionJson = writeCollection((Collection) value, getMapper(customObjectMapper,notNullObjectMapper)); + String collectionJson = writeCollection((Collection) value, getMapper(customObjectMapper, notNullObjectMapper)); jgen.writeRawValue(collectionJson); } else if (value instanceof Map) { - String mapJson = writeMap((Map) value, getMapper(customObjectMapper,notNullObjectMapper)); + String mapJson = writeMap((Map) value, getMapper(customObjectMapper, notNullObjectMapper)); jgen.writeRawValue(mapJson); } else if (value instanceof Object[] || value.getClass().isArray()) { - String arrayJson = writeArray((Object[]) value, getMapper(customObjectMapper,notNullObjectMapper)); + String arrayJson = writeArray((Object[]) value, getMapper(customObjectMapper, notNullObjectMapper)); jgen.writeRawValue(arrayJson); } else { - String json = getMapper(customObjectMapper,notNullObjectMapper).writeValueAsString(value); + String json = getMapper(customObjectMapper, notNullObjectMapper).writeValueAsString(value); // don't wrap java and javax classes as they are always available, in addition avoid double wrapping if (!className.startsWith("java.") && !className.startsWith("javax.") && !json.contains(className)) { @@ -842,14 +855,11 @@ private String writeCollection(Collection collection, ObjectMapper customObje return builder.toString(); } } - - private ObjectMapper getMapper(ObjectMapper alwaysMapper, ObjectMapper notNullMapper) - { + + private ObjectMapper getMapper(ObjectMapper alwaysMapper, ObjectMapper notNullMapper) { return jsonContext.get().isWriteNull() ? alwaysMapper : notNullMapper; } - - class CustomObjectDeserializer extends UntypedObjectDeserializer { private final Pattern VALID_JAVA_IDENTIFIER = Pattern.compile("(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)*\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"); @@ -976,6 +986,7 @@ public Object wrapCustomObject(Map result) { } return result; } + private Object[] toArray(Object element) { if (element != null) { @@ -1160,4 +1171,8 @@ public void setClassLoader(ClassLoader classLoader) { public ClassLoader getClassLoader() { return classLoader; } + + public void setXmlAnyElementsNames(boolean jsonHandleXmlAnyElementsNames) { + this.jsonHandleXmlAnyElementsNames = jsonHandleXmlAnyElementsNames; + } } diff --git a/kie-server-parent/kie-server-api/src/test/java/org/kie/server/api/marshalling/json/JSONMarshallerExtensionTest.java b/kie-server-parent/kie-server-api/src/test/java/org/kie/server/api/marshalling/json/JSONMarshallerExtensionTest.java index 0fb23ac9fd..12fed1afaa 100644 --- a/kie-server-parent/kie-server-api/src/test/java/org/kie/server/api/marshalling/json/JSONMarshallerExtensionTest.java +++ b/kie-server-parent/kie-server-api/src/test/java/org/kie/server/api/marshalling/json/JSONMarshallerExtensionTest.java @@ -16,6 +16,9 @@ package org.kie.server.api.marshalling.json; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; @@ -23,17 +26,26 @@ import java.util.HashSet; import java.util.Set; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; import org.assertj.core.api.Assertions; +import org.drools.core.command.runtime.BatchExecutionCommandImpl; import org.drools.core.command.runtime.rule.InsertObjectCommand; +import org.drools.core.util.IoUtils; import org.junit.Test; import org.kie.api.pmml.PMMLRequestData; import org.kie.server.api.marshalling.Marshaller; import org.kie.server.api.marshalling.MarshallerFactory; import org.kie.server.api.marshalling.MarshallingFormat; import org.kie.server.api.marshalling.objects.CustomPerson; +import org.kie.server.api.marshalling.objects.FreeFormItemType; +import org.kie.server.api.marshalling.objects.ItemsType; +import org.kie.server.api.marshalling.objects.StandardItemType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; public class JSONMarshallerExtensionTest { @@ -83,4 +95,61 @@ public void testObjectInsideCommand() { assertEquals(50, result.getAge()); } + + @Test + public void testPolymorphicInsideCommand() throws IOException { + Set> extraClasses = new HashSet>(); + extraClasses.add(FreeFormItemType.class); + extraClasses.add(StandardItemType.class); + extraClasses.add(ItemsType.class); + Marshaller marshaller = MarshallerFactory.getMarshaller(extraClasses, MarshallingFormat.JSON, this.getClass().getClassLoader()); + + byte[] content = new byte[0]; + String marshall = null; + URL uri = this.getClass().getClassLoader().getResource("poly_payload.json"); + try (InputStream is = uri.openStream()) { + content = IoUtils.readBytesFromInputStream(is); + BatchExecutionCommandImpl command = marshaller.unmarshall(content, BatchExecutionCommandImpl.class); + marshall = marshaller.marshall(command); + } + + BatchExecutionCommandImpl input = marshaller.unmarshall(content, BatchExecutionCommandImpl.class); + BatchExecutionCommandImpl output = marshaller.unmarshall(marshall, BatchExecutionCommandImpl.class); + + ObjectMapper mapper = new ObjectMapper().setSerializationInclusion(Include.NON_NULL); + ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter(); + + String inputStr = writer.writeValueAsString(input); + String outputStr = writer.writeValueAsString(output); + assertThat(inputStr).isEqualTo(outputStr); + } + + @Test + public void testPolymorphicInsideCommandHandle() throws IOException { + Set> extraClasses = new HashSet>(); + extraClasses.add(FreeFormItemType.class); + extraClasses.add(StandardItemType.class); + extraClasses.add(ItemsType.class); + Marshaller marshaller = MarshallerFactory.getMarshaller(extraClasses, MarshallingFormat.JSON, this.getClass().getClassLoader()); + ((JSONMarshaller) marshaller).setXmlAnyElementsNames(true); + + byte[] content = new byte[0]; + String marshall = null; + URL uri = this.getClass().getClassLoader().getResource("poly_payload.json"); + try (InputStream is = uri.openStream()) { + content = IoUtils.readBytesFromInputStream(is); + BatchExecutionCommandImpl command = marshaller.unmarshall(content, BatchExecutionCommandImpl.class); + marshall = marshaller.marshall(command); + } + + BatchExecutionCommandImpl input = marshaller.unmarshall(content, BatchExecutionCommandImpl.class); + BatchExecutionCommandImpl output = marshaller.unmarshall(marshall, BatchExecutionCommandImpl.class); + + ObjectMapper mapper = new ObjectMapper().setSerializationInclusion(Include.NON_NULL); + ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter(); + + String inputStr = writer.writeValueAsString(input); + String outputStr = writer.writeValueAsString(output); + assertThat(inputStr).isEqualTo(outputStr); + } } diff --git a/kie-server-parent/kie-server-api/src/test/java/org/kie/server/api/marshalling/objects/FreeFormItemType.java b/kie-server-parent/kie-server-api/src/test/java/org/kie/server/api/marshalling/objects/FreeFormItemType.java new file mode 100644 index 0000000000..cd6ede718f --- /dev/null +++ b/kie-server-parent/kie-server-api/src/test/java/org/kie/server/api/marshalling/objects/FreeFormItemType.java @@ -0,0 +1,62 @@ +/* + * Copyright 2024 Red Hat, Inc. and/or its affiliates. + * + * 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 org.kie.server.api.marshalling.objects; + +import java.io.Serializable; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; + + +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "FreeFormItemType", propOrder = { + "itemValue" +}) +public class FreeFormItemType implements Serializable, Cloneable +{ + + private final static long serialVersionUID = 1L; + + @XmlElement(required = true) + protected String itemValue; + + /** + * Gets the value of the itemValue property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getItemValue() { + return itemValue; + } + + /** + * Sets the value of the itemValue property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setItemValue(String value) { + this.itemValue = value; + } + +} \ No newline at end of file diff --git a/kie-server-parent/kie-server-api/src/test/java/org/kie/server/api/marshalling/objects/ItemsType.java b/kie-server-parent/kie-server-api/src/test/java/org/kie/server/api/marshalling/objects/ItemsType.java new file mode 100644 index 0000000000..5da19affa8 --- /dev/null +++ b/kie-server-parent/kie-server-api/src/test/java/org/kie/server/api/marshalling/objects/ItemsType.java @@ -0,0 +1,51 @@ +/* + * Copyright 2024 Red Hat, Inc. and/or its affiliates. + * + * 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 org.kie.server.api.marshalling.objects; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElements; +import javax.xml.bind.annotation.XmlType; + +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "ItemsType", propOrder = { + "standardItemsAndFreeformItems" +}) +public class ItemsType + implements Serializable, Cloneable +{ + + private final static long serialVersionUID = 1L; + @XmlElements({ + @XmlElement(name = "standardItem", type = StandardItemType.class), + @XmlElement(name = "freeformItem", type = FreeFormItemType.class) + }) + protected List standardItemsAndFreeformItems; + + public List getStandardItemsAndFreeformItems() { + if (standardItemsAndFreeformItems == null) { + standardItemsAndFreeformItems = new ArrayList(); + } + return this.standardItemsAndFreeformItems; + } + + +} \ No newline at end of file diff --git a/kie-server-parent/kie-server-api/src/test/java/org/kie/server/api/marshalling/objects/StandardItemType.java b/kie-server-parent/kie-server-api/src/test/java/org/kie/server/api/marshalling/objects/StandardItemType.java new file mode 100644 index 0000000000..240fef2f8a --- /dev/null +++ b/kie-server-parent/kie-server-api/src/test/java/org/kie/server/api/marshalling/objects/StandardItemType.java @@ -0,0 +1,61 @@ +/* + * Copyright 2024 Red Hat, Inc. and/or its affiliates. + * + * 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 org.kie.server.api.marshalling.objects; + +import java.io.Serializable; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.XmlValue; + + +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "StandardItemType", propOrder = { + "value" +}) +public class StandardItemType implements Serializable, Cloneable +{ + + private final static long serialVersionUID = 1L; + @XmlValue + protected String value; + + /** + * Gets the value of the value property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getValue() { + return value; + } + + /** + * Sets the value of the value property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setValue(String value) { + this.value = value; + } + +} \ No newline at end of file diff --git a/kie-server-parent/kie-server-api/src/test/resources/poly_payload.json b/kie-server-parent/kie-server-api/src/test/resources/poly_payload.json new file mode 100644 index 0000000000..c358c83f32 --- /dev/null +++ b/kie-server-parent/kie-server-api/src/test/resources/poly_payload.json @@ -0,0 +1,35 @@ +{ + "commands": [ + { + "insert": { + "object": { + "org.kie.server.api.marshalling.objects.ItemsType": { + "standardItemsAndFreeformItems": [ + { + "FreeFormItemType": { + "itemValue": "TEST 1" + } + }, + { + "FreeFormItemType": { + "itemValue": "TEST 2" + } + } + ] + } + }, + "disconnected": false, + "out-identifier": "result", + "return-object": true, + "entry-point": "DEFAULT" + } + }, + { + "fire-all-rules": { + "max": -1, + "agendaFilter": null, + "out-identifier" : "numberOfFiredRules" + } + } + ] +} \ No newline at end of file