diff --git a/build.gradle b/build.gradle index a3baa08..3397928 100644 --- a/build.gradle +++ b/build.gradle @@ -35,7 +35,7 @@ test { dependencies { compile group: "com.fasterxml.jackson.module", name: "jackson-module-jsonSchema", version: "2.9.6" - compile "com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.9.6" + compile "com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.10.1" compile group: "com.fasterxml.jackson.datatype", name: "jackson-datatype-jsr353", version: "2.9.6" compile "com.sun.xml.bind:jaxb-xjc:2.1.6" compile "com.sun.xml.ws:jaxws-tools:2.2.1" diff --git a/src/main/java/io/elastic/soap/handlers/RequestHandler.java b/src/main/java/io/elastic/soap/handlers/RequestHandler.java index a0d1247..c0e104f 100644 --- a/src/main/java/io/elastic/soap/handlers/RequestHandler.java +++ b/src/main/java/io/elastic/soap/handlers/RequestHandler.java @@ -1,7 +1,6 @@ package io.elastic.soap.handlers; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule; import io.elastic.soap.compilers.model.SoapBodyDescriptor; import io.elastic.soap.exceptions.ComponentException; import io.elastic.soap.utils.Utils; diff --git a/src/main/java/io/elastic/soap/jackson/AbstractChoiceDeserializer.java b/src/main/java/io/elastic/soap/jackson/AbstractChoiceDeserializer.java new file mode 100644 index 0000000..c72e7e2 --- /dev/null +++ b/src/main/java/io/elastic/soap/jackson/AbstractChoiceDeserializer.java @@ -0,0 +1,140 @@ +package io.elastic.soap.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.elastic.soap.utils.Utils; +import java.io.IOException; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import javax.xml.bind.JAXBElement; + +public abstract class AbstractChoiceDeserializer extends JsonDeserializer { + + protected final Class rawType; + protected final JavaType javaType; + protected final ObjectMapper mapper; + + protected AbstractChoiceDeserializer(JavaType javaType) { + this.javaType = javaType; + this.rawType = javaType.getRawClass(); + this.mapper = Utils.getConfiguredObjectMapper(); + } + + /** + * How this works: axios converts wsdl choice element to one of the following structure: + * 1. field with annotation XmlElements that contains array of XmlElement. XmlElement has property name and property type(java class of choice) + * 2. field with annotation XmlElementsRefs that contains array of XmlElementReg. XmlElementRef has property name and property type(java class of choice) + * Field created by axios usually looks like: List or List. Note in runtime we will have: List + * This method do the following: + * 1. Check that json value is possible to convert one of type provided by XmlElement or XmlElementRef annotations. + * 2. Converts value to type of field created by axios. + * @param p jackson parser. + * @param ctxt jackson context. + * @return deserialize value of choice element. + */ + @Override + public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + final JsonNode targetNode = p.getCodec().readTree(p); + nodeOneOfPossibleTypes(targetNode, this.getPossibleTypes()); + return this.mapper.convertValue(targetNode, this.rawType); + } + + /** + * @return List of possible types of choice element. + */ + public abstract List getPossibleTypes(); + + /** + * Checks that provided JsonNode from choice element has structure of one of choice element. + * In case of JAXBElement type was generated it impossible to check the structure, in this case true will be returned. + * @param node node to be checked + * @param possibleTypes possible types of node + * @throws IllegalArgumentException if node structure is not one of provided possible types. + */ + public void nodeOneOfPossibleTypes(final JsonNode node, final List possibleTypes) throws IllegalArgumentException { + if (possibleTypes.contains(JAXBElement.class)) { + return; + } + boolean result = false; + for (Class type : possibleTypes) { + result = nodeCanBeConvertedToType(node, type); + if (result) { + break; + } + } + if (!result && isNodeAndRawTypeArray(node)) { + result = handleArrayNode(node, possibleTypes); + } + if (!result) { + throw new IllegalArgumentException(constructExceptionString(node, possibleTypes)); + } + } + + /** + * @param node json node + * @return return true if node and raw type is array + */ + public boolean isNodeAndRawTypeArray(final JsonNode node) { + return node.isArray() && (this.javaType.isArrayType() || this.javaType.isCollectionLikeType()); + } + /** + * Checks each item of node over provided possible types also each item in array must have same type. + * @param arrayNode ArrayNode + * @param possibleTypes possible types of node + * @return true if each item of array node can be converted to one of possible type. + */ + public boolean handleArrayNode(JsonNode arrayNode, List possibleTypes) { + Class targetType = null; + for (JsonNode node : arrayNode) { + targetType = Optional.ofNullable(targetType).orElse(findNodeType(node, possibleTypes)); + boolean canBeConverted = nodeCanBeConvertedToType(node, targetType); + if (!canBeConverted) { + return false; + } + } + return true; + } + + /** + * @param node json node. + * @param possibleTypes possible types of node. + * @return type of node + * @throws IllegalArgumentException if node can be converted to any of provided possibleTypes + */ + private Class findNodeType(JsonNode node, List possibleTypes) { + for (Class type : possibleTypes) { + if (nodeCanBeConvertedToType(node, type)) { + return type; + } + } + throw new IllegalArgumentException(constructExceptionString(node, possibleTypes)); + } + + + /** + * + * @param node json node. + * @param type type to be checked. + * @return true if node can be converted to provided type, false otherwise. + */ + public boolean nodeCanBeConvertedToType(final JsonNode node, Class type) { + try { + this.mapper.convertValue(node, type); + return true; + } catch (IllegalArgumentException ex) { + return false; + } + } + + public String constructExceptionString(final JsonNode value, final List possibleTypes) { + final StringBuilder bd = new StringBuilder("Failed to convert choice value: "); + bd.append(value.toString()).append("to one of: "); + bd.append(possibleTypes.stream().map(Class::getSimpleName).collect(Collectors.joining(","))).append("."); + return bd.toString(); + } +} diff --git a/src/main/java/io/elastic/soap/jackson/JaxbElementsJsonValueConverter.java b/src/main/java/io/elastic/soap/jackson/JaxbElementsJsonValueConverter.java new file mode 100644 index 0000000..e40053e --- /dev/null +++ b/src/main/java/io/elastic/soap/jackson/JaxbElementsJsonValueConverter.java @@ -0,0 +1,22 @@ +package io.elastic.soap.jackson; + +import com.fasterxml.jackson.databind.util.StdConverter; +import io.elastic.soap.utils.Utils; + +import javax.json.JsonValue; +import javax.xml.bind.JAXBElement; +import java.util.List; + +public class JaxbElementsJsonValueConverter extends StdConverter, JsonValue> { + + /** + * + * @param value hack to skip check for JaxbElements coonverting + * @return list of real values. + */ + @Override + public JsonValue convert(List value) { + List rawList = value; + return Utils.getConfiguredObjectMapper().convertValue(rawList, JsonValue.class); + } +} diff --git a/src/main/java/io/elastic/soap/jackson/XmlElementRefsChoiceDeserializer.java b/src/main/java/io/elastic/soap/jackson/XmlElementRefsChoiceDeserializer.java new file mode 100644 index 0000000..0516140 --- /dev/null +++ b/src/main/java/io/elastic/soap/jackson/XmlElementRefsChoiceDeserializer.java @@ -0,0 +1,28 @@ +package io.elastic.soap.jackson; + +import com.fasterxml.jackson.databind.JavaType; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import javax.xml.bind.annotation.XmlElementRef; +import javax.xml.bind.annotation.XmlElementRefs; + +public class XmlElementRefsChoiceDeserializer extends AbstractChoiceDeserializer { + + private final XmlElementRefs annotation; + + public XmlElementRefsChoiceDeserializer(final JavaType type, final XmlElementRefs annotation) { + super(type); + this.annotation = annotation; + } + + @Override + public List getPossibleTypes() { + return Arrays.stream(this.annotation.value()) + .map(XmlElementRef::type) + .filter(c -> !c.equals(XmlElementRef.DEFAULT.class)) + .collect(Collectors.toList()); + } + + +} diff --git a/src/main/java/io/elastic/soap/jackson/XmlElementsChoiceDeserializer.java b/src/main/java/io/elastic/soap/jackson/XmlElementsChoiceDeserializer.java new file mode 100644 index 0000000..d463d52 --- /dev/null +++ b/src/main/java/io/elastic/soap/jackson/XmlElementsChoiceDeserializer.java @@ -0,0 +1,27 @@ +package io.elastic.soap.jackson; + +import com.fasterxml.jackson.databind.JavaType; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElements; + +public class XmlElementsChoiceDeserializer extends AbstractChoiceDeserializer { + + + private final XmlElements annotation; + + public XmlElementsChoiceDeserializer(final JavaType type, final XmlElements annotation) { + super(type); + this.annotation = annotation; + } + + @Override + public List getPossibleTypes() { + return Arrays.stream(this.annotation.value()) + .map(XmlElement::type) + .filter(c -> !c.equals(XmlElement.DEFAULT.class)) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/io/elastic/soap/jackson/XmlElementsIntrospector.java b/src/main/java/io/elastic/soap/jackson/XmlElementsIntrospector.java new file mode 100644 index 0000000..2c00aac --- /dev/null +++ b/src/main/java/io/elastic/soap/jackson/XmlElementsIntrospector.java @@ -0,0 +1,62 @@ +package io.elastic.soap.jackson; + +import com.fasterxml.jackson.databind.PropertyName; +import com.fasterxml.jackson.databind.introspect.Annotated; +import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; + +import java.util.*; +import java.util.stream.Collectors; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementRef; +import javax.xml.bind.annotation.XmlElementRefs; +import javax.xml.bind.annotation.XmlElements; + +public class XmlElementsIntrospector extends JacksonAnnotationIntrospector { + + /** + * Finds alias names of field in annotations XmlElements and XmlElementRefs. + * @param a annotated field. + * @return alias names of field. + */ + @Override + public List findPropertyAliases(Annotated a) { + if (a.hasAnnotation(XmlElements.class) || a.hasAnnotation(XmlElementRefs.class)) { + final List result = Optional.ofNullable(super.findPropertyAliases(a)).orElse(new ArrayList<>()); + final List names = getXmlElementsNames(a); + result.addAll(names); + return result; + } + return super.findPropertyAliases(a); + } + + /** + * Return custom deserializer in case field annotated with XmlElements or XmlElementRefs annotations. + * @param a annotated field. + * @return deserializer for field. + */ + @Override + public Object findDeserializer(Annotated a) { + if (a.hasAnnotation(XmlElementRefs.class)) { + return new XmlElementRefsChoiceDeserializer(a.getType(), a.getAnnotation(XmlElementRefs.class)); + } + if (a.hasAnnotation(XmlElements.class)) { + return new XmlElementsChoiceDeserializer(a.getType(), a.getAnnotation(XmlElements.class)); + } + return super.findDeserializer(a); + } + + @Override + public Object findSerializationConverter(Annotated a) { + if ((a.hasAnnotation(XmlElementRefs.class) || a.hasAnnotation(XmlElements.class)) && a.getType().isCollectionLikeType()) { + return new JaxbElementsJsonValueConverter(); + } + return super.findSerializationConverter(a); + } + + public List getXmlElementsNames(final Annotated a) { + if (a.hasAnnotation(XmlElements.class)) { + return Arrays.stream(a.getAnnotation(XmlElements.class).value()).map(e -> new PropertyName(e.name())).collect(Collectors.toList()); + } + return Arrays.stream(a.getAnnotation(XmlElementRefs.class).value()).map(e -> new PropertyName(e.name())).collect(Collectors.toList()); + } +} diff --git a/src/main/java/io/elastic/soap/providers/BodyMetaProvider.java b/src/main/java/io/elastic/soap/providers/BodyMetaProvider.java index f49ca8e..5775850 100644 --- a/src/main/java/io/elastic/soap/providers/BodyMetaProvider.java +++ b/src/main/java/io/elastic/soap/providers/BodyMetaProvider.java @@ -78,7 +78,7 @@ public JsonObject getMetaModel(final JsonObject configuration) { final String portTypeName = wsdl.getBinding(bindingName).getPortType().getName(); final Operation operation = wsdl.getOperation(operationName, portTypeName); final JsonObject in = generateSchema(operation.getInput().getMessage()); - final JsonObject out = generateSchema(operation.getInput().getMessage()); + final JsonObject out = generateSchema(operation.getOutput().getMessage()); final JsonObject result = Json.createObjectBuilder() .add("in", in) .add("out", out) diff --git a/src/main/java/io/elastic/soap/utils/Utils.java b/src/main/java/io/elastic/soap/utils/Utils.java index 253a5d2..010bbee 100644 --- a/src/main/java/io/elastic/soap/utils/Utils.java +++ b/src/main/java/io/elastic/soap/utils/Utils.java @@ -1,8 +1,10 @@ package io.elastic.soap.utils; import ch.qos.logback.classic.Level; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr353.JSR353Module; import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule; import com.google.common.base.CaseFormat; @@ -13,6 +15,7 @@ import io.elastic.soap.compilers.JaxbCompiler; import io.elastic.soap.compilers.model.SoapBodyDescriptor; import io.elastic.soap.exceptions.ComponentException; +import io.elastic.soap.jackson.XmlElementsIntrospector; import io.elastic.soap.services.SoapCallService; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -62,11 +65,14 @@ private Utils() { * @return ObjectMapper instance */ public static ObjectMapper getConfiguredObjectMapper() { - final JaxbAnnotationModule module = new JaxbAnnotationModule(); final ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS, true); + objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); + objectMapper.configure(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED, true); objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); + objectMapper.setAnnotationIntrospector(new XmlElementsIntrospector()); objectMapper.registerModule(new JSR353Module()); - objectMapper.registerModule(module); + objectMapper.registerModule(new JaxbAnnotationModule()); return objectMapper; } diff --git a/src/test/java/io/elastic/soap/jackson/ChoiceMetadataTest.java b/src/test/java/io/elastic/soap/jackson/ChoiceMetadataTest.java new file mode 100644 index 0000000..628d673 --- /dev/null +++ b/src/test/java/io/elastic/soap/jackson/ChoiceMetadataTest.java @@ -0,0 +1,64 @@ +package io.elastic.soap.jackson; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.elastic.soap.handlers.RequestHandler; +import io.elastic.soap.utils.Utils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import javax.json.*; +import javax.xml.bind.annotation.XmlElements; +import java.io.InputStream; +import java.io.InputStreamReader; + +public class ChoiceMetadataTest { + + @Test + public void serializeClassWithXmlElementsAnnotation() throws ClassNotFoundException { + final RequestHandler handler = new RequestHandler(); + final String weatherDescription = "XmlElementsChoice"; + readResourceFileAsJsonArray("choicesElements.json").stream().map(JsonValue::asJsonObject).forEach(o -> { + System.out.println(o); + final XmlElementsChoice result = this.wrapAndTest(handler, o, weatherDescription, XmlElementsChoice.class); + Assertions.assertNotNull(result); + Assertions.assertNotNull(result.intFieldOrStringFieldOrComplexType); + final ObjectMapper mapper = Utils.getConfiguredObjectMapper(); + Assertions.assertEquals(getFirstKeyOfJsonObject(o.getJsonObject("XmlElementsChoice")), mapper.convertValue(result, JsonObject.class).get("intFieldOrStringFieldOrComplexType")); + }); + } + + @Test + public void serializeClassWithXmlElementRefsAnnotation() throws ClassNotFoundException { + final RequestHandler handler = new RequestHandler(); + final String weatherDescription = "XmlElementRefsChoice"; + readResourceFileAsJsonArray("choicesRefs.json") + .stream() + .map(JsonValue::asJsonObject) + .forEach(o -> { + final XmlElementRefsChoice result = this.wrapAndTest(handler, o, weatherDescription, XmlElementRefsChoice.class); + Assertions.assertNotNull(result); + Assertions.assertNotNull(result.intFieldOrStringField1OrStringField2); + final ObjectMapper mapper = Utils.getConfiguredObjectMapper(); + Assertions.assertEquals(getFirstKeyOfJsonObject(o.getJsonObject("XmlElementRefsChoice")), mapper.convertValue(result, JsonObject.class).get("intFieldOrStringField1OrStringField2")); + }); + } + + public T wrapAndTest(RequestHandler handler, JsonObject request, String elementName, Class clazz) { + try { + return handler.getObjectFromJson(request, elementName, clazz); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public JsonArray readResourceFileAsJsonArray(final String path) { + InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(path); + JsonReader jsonReader = Json.createReader(new InputStreamReader(inputStream)); + JsonArray choices = jsonReader.readArray(); + jsonReader.close(); + return choices; + } + public JsonValue getFirstKeyOfJsonObject(JsonObject o) { + return o.values().iterator().next(); + } +} diff --git a/src/test/java/io/elastic/soap/jackson/ComplexType.java b/src/test/java/io/elastic/soap/jackson/ComplexType.java new file mode 100644 index 0000000..e0393f9 --- /dev/null +++ b/src/test/java/io/elastic/soap/jackson/ComplexType.java @@ -0,0 +1,118 @@ + +package io.elastic.soap.jackson; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for ComplexType complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType name="ComplexType">
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <sequence>
+ *         <element name="Success" type="{http://www.w3.org/2001/XMLSchema}boolean" minOccurs="0"/>
+ *         <element name="ResponseText" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
+ *         <element name="City" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
+ *       </sequence>
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "ComplexType", propOrder = { + "success", + "responseText", + "city" +}) +public class ComplexType { + + @XmlElement(name = "Success") + protected Boolean success; + @XmlElement(name = "ResponseText") + protected String responseText; + @XmlElement(name = "City") + protected String city; + + /** + * Gets the value of the success property. + * + * @return + * possible object is + * {@link Boolean } + * + */ + public Boolean isSuccess() { + return success; + } + + /** + * Sets the value of the success property. + * + * @param value + * allowed object is + * {@link Boolean } + * + */ + public void setSuccess(Boolean value) { + this.success = value; + } + + /** + * Gets the value of the responseText property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getResponseText() { + return responseText; + } + + /** + * Sets the value of the responseText property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setResponseText(String value) { + this.responseText = value; + } + + /** + * Gets the value of the city property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getCity() { + return city; + } + + /** + * Sets the value of the city property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setCity(String value) { + this.city = value; + } + +} diff --git a/src/test/java/io/elastic/soap/jackson/XmlElementRefsChoice.java b/src/test/java/io/elastic/soap/jackson/XmlElementRefsChoice.java new file mode 100644 index 0000000..8d8fb49 --- /dev/null +++ b/src/test/java/io/elastic/soap/jackson/XmlElementRefsChoice.java @@ -0,0 +1,87 @@ + +package io.elastic.soap.jackson; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElementRef; +import javax.xml.bind.annotation.XmlElementRefs; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for XmlElementRefsChoice complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType name="XmlElementRefsChoice">
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <choice maxOccurs="unbounded" minOccurs="0">
+ *         <element name="IntField" type="{http://www.w3.org/2001/XMLSchema}int" minOccurs="0"/>
+ *         <element name="StringField1" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
+ *         <element name="StringField2" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
+ *         <element name="StringField3" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
+ *         <element name="StringField4" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
+ *       </choice>
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "XmlElementRefsChoice", propOrder = { + "intFieldOrStringField1OrStringField2" +}) +public class XmlElementRefsChoice { + + @XmlElementRefs({ + @XmlElementRef(name = "StringField1", namespace = "http://elatic.io/test", type = JAXBElement.class, required = false), + @XmlElementRef(name = "StringField3", namespace = "http://elatic.io/test", type = JAXBElement.class, required = false), + @XmlElementRef(name = "StringField4", namespace = "http://elatic.io/test", type = JAXBElement.class, required = false), + @XmlElementRef(name = "IntField", namespace = "http://elatic.io/test", type = JAXBElement.class, required = false), + @XmlElementRef(name = "StringField2", namespace = "http://elatic.io/test", type = JAXBElement.class, required = false), + @XmlElementRef(name = "ComplexType", namespace = "http://elatic.io/test", type = JAXBElement.class, required = false) + }) + protected List> intFieldOrStringField1OrStringField2; + + /** + * Gets the value of the intFieldOrStringField1OrStringField2 property. + * + *

+ * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the intFieldOrStringField1OrStringField2 property. + * + *

+ * For example, to add a new item, do as follows: + *

+     *    getIntFieldOrStringField1OrStringField2().add(newItem);
+     * 
+ * + * + *

+ * Objects of the following type(s) are allowed in the list + * {@link JAXBElement }{@code <}{@link String }{@code >} + * {@link JAXBElement }{@code <}{@link String }{@code >} + * {@link JAXBElement }{@code <}{@link Integer }{@code >} + * {@link JAXBElement }{@code <}{@link String }{@code >} + * {@link JAXBElement }{@code <}{@link String }{@code >} + * + * + */ + public List> getIntFieldOrStringField1OrStringField2() { + if (intFieldOrStringField1OrStringField2 == null) { + intFieldOrStringField1OrStringField2 = new ArrayList>(); + } + return this.intFieldOrStringField1OrStringField2; + } + +} diff --git a/src/test/java/io/elastic/soap/jackson/XmlElementsChoice.java b/src/test/java/io/elastic/soap/jackson/XmlElementsChoice.java new file mode 100644 index 0000000..32a10bb --- /dev/null +++ b/src/test/java/io/elastic/soap/jackson/XmlElementsChoice.java @@ -0,0 +1,78 @@ + +package io.elastic.soap.jackson; + +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; + + +/** + *

Java class for XmlElementsChoice complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType name="XmlElementsChoice">
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <choice maxOccurs="unbounded" minOccurs="0">
+ *         <element name="IntField" type="{http://www.w3.org/2001/XMLSchema}int" minOccurs="0"/>
+ *         <element name="StringField" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
+ *         <element name="ComplexType" type="{http://elatic.io/test}ComplexType" minOccurs="0"/>
+ *       </choice>
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "XmlElementsChoice", propOrder = { + "intFieldOrStringFieldOrComplexType" +}) +public class XmlElementsChoice { + + @XmlElements({ + @XmlElement(name = "IntField", type = Integer.class), + @XmlElement(name = "StringField", type = String.class), + @XmlElement(name = "ComplexType", type = ComplexType.class) + }) + protected List intFieldOrStringFieldOrComplexType; + + /** + * Gets the value of the intFieldOrStringFieldOrComplexType property. + * + *

+ * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the intFieldOrStringFieldOrComplexType property. + * + *

+ * For example, to add a new item, do as follows: + *

+     *    getIntFieldOrStringFieldOrComplexType().add(newItem);
+     * 
+ * + * + *

+ * Objects of the following type(s) are allowed in the list + * {@link Integer } + * {@link String } + * {@link ComplexType } + * + * + */ + public List getIntFieldOrStringFieldOrComplexType() { + if (intFieldOrStringFieldOrComplexType == null) { + intFieldOrStringFieldOrComplexType = new ArrayList(); + } + return this.intFieldOrStringFieldOrComplexType; + } + +} diff --git a/src/test/resources/choicesElements.json b/src/test/resources/choicesElements.json new file mode 100644 index 0000000..9edf65b --- /dev/null +++ b/src/test/resources/choicesElements.json @@ -0,0 +1,104 @@ +[ + { + "XmlElementsChoice": { + "intFieldOrStringFieldOrComplexType": [ + "1", + "2" + ] + } + }, + { + "XmlElementsChoice": { + "intFieldOrStringFieldOrComplexType": [ + 1, + 2 + ] + } + }, + { + "XmlElementsChoice": { + "intFieldOrStringFieldOrComplexType": [ + { + "Success": true, + "ResponseText": "Test", + "City": "Test" + }, + { + "Success": false, + "ResponseText": "Test2", + "City": "Test2" + } + ] + } + }, + { + "XmlElementsChoice": { + "intFieldOrStringFieldOrComplexType": "1" + } + }, + { + "XmlElementsChoice": { + "intFieldOrStringFieldOrComplexType": 2 + } + }, + { + "XmlElementsChoice": { + "intFieldOrStringFieldOrComplexType": { + "Success": true, + "ResponseText": "Test", + "City": "Test" + } + } + }, + { + "XmlElementsChoice": { + "StringField": [ + "1", + "3" + ] + } + }, + { + "XmlElementsChoice": { + "IntField": [ + 1, + 2 + ] + } + }, + { + "XmlElementsChoice": { + "ComplexType": [ + { + "Success": true, + "ResponseText": "Test", + "City": "Test" + }, + { + "Success": false, + "ResponseText": "Test2", + "City": "Test2" + } + ] + } + }, + { + "XmlElementsChoice": { + "StringField": "4" + } + }, + { + "XmlElementsChoice": { + "IntField": 1 + } + }, + { + "XmlElementsChoice": { + "ComplexType": { + "Success": true, + "ResponseText": "Test", + "City": "Test" + } + } + } +] \ No newline at end of file diff --git a/src/test/resources/choicesRefs.json b/src/test/resources/choicesRefs.json new file mode 100644 index 0000000..c013f6c --- /dev/null +++ b/src/test/resources/choicesRefs.json @@ -0,0 +1,104 @@ +[ + { + "XmlElementRefsChoice": { + "intFieldOrStringField1OrStringField2": [ + "1", + "2" + ] + } + }, + { + "XmlElementRefsChoice": { + "intFieldOrStringField1OrStringField2": [ + 1, + 2 + ] + } + }, + { + "XmlElementRefsChoice": { + "intFieldOrStringField1OrStringField2": [ + { + "Success": true, + "ResponseText": "Test", + "City": "Test" + }, + { + "Success": false, + "ResponseText": "Test2", + "City": "Test2" + } + ] + } + }, + { + "XmlElementRefsChoice": { + "intFieldOrStringField1OrStringField2": "1" + } + }, + { + "XmlElementRefsChoice": { + "intFieldOrStringField1OrStringField2": 2 + } + }, + { + "XmlElementRefsChoice": { + "intFieldOrStringField1OrStringField2": { + "Success": true, + "ResponseText": "Test", + "City": "Test" + } + } + }, + { + "XmlElementRefsChoice": { + "StringField1": [ + "1", + "3" + ] + } + }, + { + "XmlElementRefsChoice": { + "IntField": [ + 1, + 2 + ] + } + }, + { + "XmlElementRefsChoice": { + "ComplexType": [ + { + "Success": true, + "ResponseText": "Test", + "City": "Test" + }, + { + "Success": false, + "ResponseText": "Test2", + "City": "Test2" + } + ] + } + }, + { + "XmlElementRefsChoice": { + "StringField2": "4" + } + }, + { + "XmlElementRefsChoice": { + "IntField": 1 + } + }, + { + "XmlElementRefsChoice": { + "ComplexType": { + "Success": true, + "ResponseText": "Test", + "City": "Test" + } + } + } +] \ No newline at end of file