From 5ce39ecf6322412d66343b338e9a7cf90e761715 Mon Sep 17 00:00:00 2001 From: uaArsen Date: Thu, 28 Nov 2019 18:02:02 +0200 Subject: [PATCH 1/6] Fix choice --- build.gradle | 2 +- .../elastic/soap/handlers/RequestHandler.java | 1 - .../soap/providers/BodyMetaProvider.java | 2 +- .../java/io/elastic/soap/utils/Utils.java | 4 + .../soap/utils/XMLElementRefDeserializer.java | 29 ++ .../soap/utils/XMLElementsIntrospector.java | 44 +++ .../soap/providers/ChoiceMetadataTest.java | 116 ++++++ src/test/resources/choice.wsdl | 353 ++++++++++++++++++ src/test/resources/choices.json | 61 +++ 9 files changed, 609 insertions(+), 3 deletions(-) create mode 100644 src/main/java/io/elastic/soap/utils/XMLElementRefDeserializer.java create mode 100644 src/main/java/io/elastic/soap/utils/XMLElementsIntrospector.java create mode 100644 src/test/java/io/elastic/soap/providers/ChoiceMetadataTest.java create mode 100644 src/test/resources/choice.wsdl create mode 100644 src/test/resources/choices.json 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/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..505f757 100644 --- a/src/main/java/io/elastic/soap/utils/Utils.java +++ b/src/main/java/io/elastic/soap/utils/Utils.java @@ -1,6 +1,7 @@ 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.datatype.jsr353.JSR353Module; @@ -64,7 +65,10 @@ private Utils() { 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(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); + objectMapper.setAnnotationIntrospector(new XMLElementsIntrospector()); objectMapper.registerModule(new JSR353Module()); objectMapper.registerModule(module); return objectMapper; diff --git a/src/main/java/io/elastic/soap/utils/XMLElementRefDeserializer.java b/src/main/java/io/elastic/soap/utils/XMLElementRefDeserializer.java new file mode 100644 index 0000000..17231e4 --- /dev/null +++ b/src/main/java/io/elastic/soap/utils/XMLElementRefDeserializer.java @@ -0,0 +1,29 @@ +package io.elastic.soap.utils; + +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 java.io.IOException; +import javax.xml.bind.annotation.XmlElementRefs; + +public class XMLElementRefDeserializer extends JsonDeserializer { + + private final Class rawType; + private final XmlElementRefs annotation; + + public XMLElementRefDeserializer(final JavaType type, final XmlElementRefs annotation) { + this.annotation = annotation; + this.rawType = type.getRawClass(); + } + + @Override + public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + final ObjectMapper m = Utils.getConfiguredObjectMapper(); + final JsonNode targetNode = p.getCodec().readTree(p); + return m.convertValue(targetNode, this.rawType); + } + +} diff --git a/src/main/java/io/elastic/soap/utils/XMLElementsIntrospector.java b/src/main/java/io/elastic/soap/utils/XMLElementsIntrospector.java new file mode 100644 index 0000000..cebee28 --- /dev/null +++ b/src/main/java/io/elastic/soap/utils/XMLElementsIntrospector.java @@ -0,0 +1,44 @@ +package io.elastic.soap.utils; + +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.PropertyName; +import com.fasterxml.jackson.databind.introspect.Annotated; +import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import javax.xml.bind.annotation.XmlElementRefs; +import javax.xml.bind.annotation.XmlElements; + +public class XMLElementsIntrospector extends JacksonAnnotationIntrospector { + + @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); + } + + 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()); + } + + @Override + public Object findDeserializer(Annotated a) { + if (a.hasAnnotation(XmlElementRefs.class)) { + List names = getXMLElementsNames(a).stream().map(PropertyName::getSimpleName).collect(Collectors.toList()); + names.add(a.getName()); + return new XMLElementRefDeserializer(a.getType(), a.getAnnotation(XmlElementRefs.class)); + } + return super.findDeserializer(a); + } +} diff --git a/src/test/java/io/elastic/soap/providers/ChoiceMetadataTest.java b/src/test/java/io/elastic/soap/providers/ChoiceMetadataTest.java new file mode 100644 index 0000000..e52f08a --- /dev/null +++ b/src/test/java/io/elastic/soap/providers/ChoiceMetadataTest.java @@ -0,0 +1,116 @@ +package io.elastic.soap.providers; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import com.predic8.wsdl.Definitions; +import com.predic8.wsdl.WSDLParser; +import io.elastic.soap.AppConstants; +import io.elastic.soap.compilers.JaxbCompiler; +import io.elastic.soap.handlers.RequestHandler; +import io.elastic.soap.services.WSDLService; +import io.elastic.soap.services.impls.HttpWSDLService; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Arrays; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.json.JsonReader; +import javax.json.JsonValue; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class ChoiceMetadataTest { + + private static BodyMetaProvider provider; + private static JsonObject config; + private final static String WSDL_URL = "src/test/resources/choice.wsdl"; + private static String[] arrayOfDirsToDelete = {"src/com", "src/io"}; + + @BeforeAll + public static void beforeAll() throws Throwable { + Definitions definitions = getDefinitions(WSDL_URL); + config = Json.createObjectBuilder() + .add(AppConstants.BINDING_CONFIG_NAME, "WeatherSoap") + .add(AppConstants.OPERATION_CONFIG_NAME, "GetWeatherInformation") + .add(AppConstants.WSDL_CONFIG_NAME, "http://weather.com?wsdl") + .add("auth", + Json.createObjectBuilder().add("type", "No Auth") + .add("basic", Json.createObjectBuilder().add("username", "") + .add("password", "") + .build()) + ) + .build(); + + provider = new BodyMetaProvider(); + WSDLService service = spy(new HttpWSDLService()); + provider.setWsdlService(service); + doReturn(definitions).when(service).getWSDL(any(JsonObject.class)); + JaxbCompiler.generateAndLoadJaxbStructure(WSDL_URL); + JaxbCompiler.putToCache("http://weather.com?wsdl", AppConstants.GENERATED_RESOURCES_DIR); + } + + + @AfterAll + public static void cleanup() throws IOException { + for (final String dirName : Arrays.asList(arrayOfDirsToDelete)) { + final File dir = new File(dirName); + if (dir.exists()) { +// FileUtils.deleteDirectory(dir); + } + } + +// FileUtils.cleanDirectory(new File(AppConstants.GENERATED_RESOURCES_DIR)); + } + + public static Definitions getDefinitions(final String wsdlPath) { + final WSDLParser parser = new WSDLParser(); + return parser.parse(wsdlPath); + } + + @Test + public void choiceMetadataGeneration() { + final JsonObject object = provider.getMetaModel(config); + Assertions.assertNotNull(object.get("in")); + Assertions.assertNotNull(object.get("out")); + } + + @Test + public void serializerTest() throws ClassNotFoundException, IOException { + final RequestHandler handler = new RequestHandler(); + final String weatherDescription = "WeatherDescription"; + final Class clazz = Class.forName("com.cdyne.ws.weatherws.WeatherDescription"); + class DummyTester { + + public Object test(JsonObject o) { + try { + return handler.getObjectFromJson(o, weatherDescription, clazz); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + } + DummyTester tester = new DummyTester(); + readResourceFileAsJsonArray("choices.json").stream().map(JsonValue::asJsonObject).forEach(o -> { + System.out.println(o); + Object result = tester.test(o); + Assertions.assertNotNull(result); + }); + } + + private 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; + } + +} diff --git a/src/test/resources/choice.wsdl b/src/test/resources/choice.wsdl new file mode 100644 index 0000000..a23e6c0 --- /dev/null +++ b/src/test/resources/choice.wsdl @@ -0,0 +1,353 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Gets Information for each WeatherID + + + + + Allows you to get your City Forecast Over the Next 7 Days, which is updated hourly. U.S. Only + + + + + Allows you to get your City's Weather, which is updated hourly. U.S. Only + + + + + + + Gets Information for each WeatherID + + + + + Allows you to get your City Forecast Over the Next 7 Days, which is updated hourly. U.S. Only + + + + + Allows you to get your City's Weather, which is updated hourly. U.S. Only + + + + + + + Gets Information for each WeatherID + + + + + Allows you to get your City Forecast Over the Next 7 Days, which is updated hourly. U.S. Only + + + + + Allows you to get your City's Weather, which is updated hourly. U.S. Only + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/choices.json b/src/test/resources/choices.json new file mode 100644 index 0000000..0d46457 --- /dev/null +++ b/src/test/resources/choices.json @@ -0,0 +1,61 @@ +[ + { + "WeatherDescription": { + "responseURLOrOBSResultOrOBSResult1": ["1", 2] + } + }, + { + "WeatherDescription": { + "responseURLOrOBSResultOrOBSResult1": ["1", "2"] + } + }, + { + "WeatherDescription": { + "responseURLOrOBSResultOrOBSResult1": [1, 2] + } + }, + { + "WeatherDescription": { + "responseURLOrOBSResultOrOBSResult1": ["1", "2"] + } + }, + { + "WeatherDescription": { + "responseURLOrOBSResultOrOBSResult1": ["1", "2"] + } + }, + { + "WeatherDescription": { + "ResponseURL": [ + "1", + "3" + ] + } + }, + { + "WeatherDescription": { + "ResponseURL": ["1", "2"] + } + }, + { + "WeatherDescription": { + "OBSResult": ["1", "2"] + } + }, + { + "WeatherDescription": { + "ResponseURL": "1" + } + }, + { + "WeatherDescription": { + "OBSResult": "4" + } + }, + { + "WeatherDescription": { + "ResponseURL": "1", + "OBSResult": 1 + } + } +] \ No newline at end of file From f9150be2b717cca5394f5d2756a9722fc639d4b3 Mon Sep 17 00:00:00 2001 From: uaArsen Date: Fri, 29 Nov 2019 14:10:50 +0200 Subject: [PATCH 2/6] Add handling for XMLElement --- .../soap/utils/XMLElementsIntrospector.java | 22 ++++++++++++------- src/test/resources/choice.wsdl | 7 +----- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/main/java/io/elastic/soap/utils/XMLElementsIntrospector.java b/src/main/java/io/elastic/soap/utils/XMLElementsIntrospector.java index cebee28..279c696 100644 --- a/src/main/java/io/elastic/soap/utils/XMLElementsIntrospector.java +++ b/src/main/java/io/elastic/soap/utils/XMLElementsIntrospector.java @@ -1,6 +1,5 @@ package io.elastic.soap.utils; -import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.PropertyName; import com.fasterxml.jackson.databind.introspect.Annotated; import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; @@ -9,6 +8,7 @@ import java.util.List; import java.util.Optional; import java.util.stream.Collectors; +import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementRefs; import javax.xml.bind.annotation.XmlElements; @@ -22,14 +22,13 @@ public List findPropertyAliases(Annotated a) { result.addAll(names); return result; } - return super.findPropertyAliases(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()); + if (a.hasAnnotation(XmlElement.class)) { + final List result = Optional.ofNullable(super.findPropertyAliases(a)).orElse(new ArrayList<>()); + XmlElement element = a.getAnnotation(XmlElement.class); + result.add(new PropertyName(element.name())); + return result; } - return Arrays.stream(a.getAnnotation(XmlElementRefs.class).value()).map(e -> new PropertyName(e.name())).collect(Collectors.toList()); + return super.findPropertyAliases(a); } @Override @@ -41,4 +40,11 @@ public Object findDeserializer(Annotated a) { } return super.findDeserializer(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/test/resources/choice.wsdl b/src/test/resources/choice.wsdl index a23e6c0..02e7fc9 100644 --- a/src/test/resources/choice.wsdl +++ b/src/test/resources/choice.wsdl @@ -20,12 +20,7 @@ - - - - - - + From c03e44990d5b9a4092219d78d63fba037f808231 Mon Sep 17 00:00:00 2001 From: uaArsen Date: Fri, 29 Nov 2019 17:11:54 +0200 Subject: [PATCH 3/6] Finish tests --- .../jackson/AbstractChoiceDeserializer.java | 106 ++++++ .../XMLElementRefsChoiceDeserializer.java | 25 ++ .../XMLElementsChoiceDeserializer.java | 26 ++ .../XMLElementsIntrospector.java | 6 + .../soap/utils/XMLElementRefDeserializer.java | 29 -- .../ChoiceMetadataTest.java | 44 +-- .../soap/jackson/XmlElementsChoice.java | 5 + src/test/resources/choice.wsdl | 348 ------------------ src/test/resources/choices.json | 61 --- src/test/resources/choicesElements.json | 104 ++++++ src/test/resources/choicesRefs.json | 104 ++++++ 11 files changed, 396 insertions(+), 462 deletions(-) create mode 100644 src/main/java/io/elastic/soap/jackson/AbstractChoiceDeserializer.java create mode 100644 src/main/java/io/elastic/soap/jackson/XMLElementRefsChoiceDeserializer.java create mode 100644 src/main/java/io/elastic/soap/jackson/XMLElementsChoiceDeserializer.java rename src/main/java/io/elastic/soap/{utils => jackson}/XMLElementsIntrospector.java (86%) delete mode 100644 src/main/java/io/elastic/soap/utils/XMLElementRefDeserializer.java rename src/test/java/io/elastic/soap/{providers => jackson}/ChoiceMetadataTest.java (71%) create mode 100644 src/test/java/io/elastic/soap/jackson/XmlElementsChoice.java delete mode 100644 src/test/resources/choice.wsdl delete mode 100644 src/test/resources/choices.json create mode 100644 src/test/resources/choicesElements.json create mode 100644 src/test/resources/choicesRefs.json 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..b17b189 --- /dev/null +++ b/src/main/java/io/elastic/soap/jackson/AbstractChoiceDeserializer.java @@ -0,0 +1,106 @@ +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 AbstractJaxbDeserializer extends JsonDeserializer { + + protected final Class rawType; + protected final JavaType type; + protected final ObjectMapper mapper; + + protected AbstractJaxbDeserializer(JavaType type) { + this.type = type; + this.rawType = type.getRawClass(); + this.mapper = Utils.getConfiguredObjectMapper(); + } + + @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); + } + + 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 && node.isArray() && (this.type.isArrayType() || this.type.isCollectionLikeType())) { + result = handleArrayNode(node, possibleTypes); + } + if (!result) { + throw new IllegalArgumentException(constructExceptionString(node, possibleTypes)); + } + } + + /** + * Checks each item of node over provided possible types. + * @param arrayNode ArrayNode + * @param possibleTypes possible types of node + * @return + */ + 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; + } + + private Class findNodeType(JsonNode node, List possibleTypes) { + for (Class type : possibleTypes) { + if (nodeCanBeConvertedToType(node, type)) { + return type; + } + } + throw new IllegalArgumentException(constructExceptionString(node, possibleTypes)); + } + + + 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) { + StringBuilder bd = new StringBuilder("Failed to convert value: "); + bd.append(value.toPrettyString()).append("\n 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/XMLElementRefsChoiceDeserializer.java b/src/main/java/io/elastic/soap/jackson/XMLElementRefsChoiceDeserializer.java new file mode 100644 index 0000000..bec77e4 --- /dev/null +++ b/src/main/java/io/elastic/soap/jackson/XMLElementRefsChoiceDeserializer.java @@ -0,0 +1,25 @@ +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.JAXBElement; +import javax.xml.bind.annotation.XmlElementRef; +import javax.xml.bind.annotation.XmlElementRefs; + +public class XMLElementRefsDeserializer extends AbstractJaxbDeserializer { + + private final XmlElementRefs annotation; + + public XMLElementRefsDeserializer(final JavaType type, final XmlElementRefs annotation) { + super(type); + this.annotation = annotation; + } + + @Override + public List getPossibleTypes() { + return Arrays.stream(this.annotation.value()).map(XmlElementRef::type).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..0622fc3 --- /dev/null +++ b/src/main/java/io/elastic/soap/jackson/XMLElementsChoiceDeserializer.java @@ -0,0 +1,26 @@ +package io.elastic.soap.jackson; + +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +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 XMLElementsDeserializer extends AbstractJaxbDeserializer { + + + private final XmlElements annotation; + + public XMLElementsDeserializer(final JavaType type, final XmlElements annotation) { + super(type); + this.annotation = annotation; + } + + @Override + public List getPossibleTypes() { + return Arrays.stream(this.annotation.value()).map(XmlElement::type).collect(Collectors.toList()); + } +} diff --git a/src/main/java/io/elastic/soap/utils/XMLElementsIntrospector.java b/src/main/java/io/elastic/soap/jackson/XMLElementsIntrospector.java similarity index 86% rename from src/main/java/io/elastic/soap/utils/XMLElementsIntrospector.java rename to src/main/java/io/elastic/soap/jackson/XMLElementsIntrospector.java index 279c696..ffc6074 100644 --- a/src/main/java/io/elastic/soap/utils/XMLElementsIntrospector.java +++ b/src/main/java/io/elastic/soap/jackson/XMLElementsIntrospector.java @@ -9,6 +9,7 @@ import java.util.Optional; 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; @@ -38,6 +39,11 @@ public Object findDeserializer(Annotated a) { names.add(a.getName()); return new XMLElementRefDeserializer(a.getType(), a.getAnnotation(XmlElementRefs.class)); } + if (a.hasAnnotation(XmlElements.class)) { + List names = getXMLElementsNames(a).stream().map(PropertyName::getSimpleName).collect(Collectors.toList()); + names.add(a.getName()); + return new XMLElementsDeserializer(a.getType(), a.getAnnotation(XmlElementRefs.class)); + } return super.findDeserializer(a); } diff --git a/src/main/java/io/elastic/soap/utils/XMLElementRefDeserializer.java b/src/main/java/io/elastic/soap/utils/XMLElementRefDeserializer.java deleted file mode 100644 index 17231e4..0000000 --- a/src/main/java/io/elastic/soap/utils/XMLElementRefDeserializer.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.elastic.soap.utils; - -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 java.io.IOException; -import javax.xml.bind.annotation.XmlElementRefs; - -public class XMLElementRefDeserializer extends JsonDeserializer { - - private final Class rawType; - private final XmlElementRefs annotation; - - public XMLElementRefDeserializer(final JavaType type, final XmlElementRefs annotation) { - this.annotation = annotation; - this.rawType = type.getRawClass(); - } - - @Override - public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - final ObjectMapper m = Utils.getConfiguredObjectMapper(); - final JsonNode targetNode = p.getCodec().readTree(p); - return m.convertValue(targetNode, this.rawType); - } - -} diff --git a/src/test/java/io/elastic/soap/providers/ChoiceMetadataTest.java b/src/test/java/io/elastic/soap/jackson/ChoiceMetadataTest.java similarity index 71% rename from src/test/java/io/elastic/soap/providers/ChoiceMetadataTest.java rename to src/test/java/io/elastic/soap/jackson/ChoiceMetadataTest.java index e52f08a..6f3e99b 100644 --- a/src/test/java/io/elastic/soap/providers/ChoiceMetadataTest.java +++ b/src/test/java/io/elastic/soap/jackson/ChoiceMetadataTest.java @@ -21,6 +21,7 @@ import javax.json.JsonObject; import javax.json.JsonReader; import javax.json.JsonValue; +import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -30,16 +31,16 @@ public class ChoiceMetadataTest { private static BodyMetaProvider provider; private static JsonObject config; - private final static String WSDL_URL = "src/test/resources/choice.wsdl"; + private final static String WSDL_URL = "src/test/resources/choices.wsdl"; private static String[] arrayOfDirsToDelete = {"src/com", "src/io"}; @BeforeAll public static void beforeAll() throws Throwable { Definitions definitions = getDefinitions(WSDL_URL); config = Json.createObjectBuilder() - .add(AppConstants.BINDING_CONFIG_NAME, "WeatherSoap") - .add(AppConstants.OPERATION_CONFIG_NAME, "GetWeatherInformation") - .add(AppConstants.WSDL_CONFIG_NAME, "http://weather.com?wsdl") + .add(AppConstants.BINDING_CONFIG_NAME, "SOAP11") + .add(AppConstants.OPERATION_CONFIG_NAME, "GetXmlElementRefsChoice") + .add(AppConstants.WSDL_CONFIG_NAME, "http://elatic.io/test/") .add("auth", Json.createObjectBuilder().add("type", "No Auth") .add("basic", Json.createObjectBuilder().add("username", "") @@ -53,7 +54,7 @@ public static void beforeAll() throws Throwable { provider.setWsdlService(service); doReturn(definitions).when(service).getWSDL(any(JsonObject.class)); JaxbCompiler.generateAndLoadJaxbStructure(WSDL_URL); - JaxbCompiler.putToCache("http://weather.com?wsdl", AppConstants.GENERATED_RESOURCES_DIR); + JaxbCompiler.putToCache("http://elatic.io/test/", AppConstants.GENERATED_RESOURCES_DIR); } @@ -62,11 +63,11 @@ public static void cleanup() throws IOException { for (final String dirName : Arrays.asList(arrayOfDirsToDelete)) { final File dir = new File(dirName); if (dir.exists()) { -// FileUtils.deleteDirectory(dir); + FileUtils.deleteDirectory(dir); } } -// FileUtils.cleanDirectory(new File(AppConstants.GENERATED_RESOURCES_DIR)); + FileUtils.cleanDirectory(new File(AppConstants.GENERATED_RESOURCES_DIR)); } public static Definitions getDefinitions(final String wsdlPath) { @@ -82,30 +83,25 @@ public void choiceMetadataGeneration() { } @Test - public void serializerTest() throws ClassNotFoundException, IOException { + public void serializeClassWithXmlElementsAnnotation() throws ClassNotFoundException { final RequestHandler handler = new RequestHandler(); final String weatherDescription = "WeatherDescription"; - final Class clazz = Class.forName("com.cdyne.ws.weatherws.WeatherDescription"); - class DummyTester { - - public Object test(JsonObject o) { - try { - return handler.getObjectFromJson(o, weatherDescription, clazz); - } catch (Exception e) { - throw new RuntimeException(e); - } - - } - } - DummyTester tester = new DummyTester(); - readResourceFileAsJsonArray("choices.json").stream().map(JsonValue::asJsonObject).forEach(o -> { + final Class clazz = Class.forName("io.elastic.test.XmlElementsChoice"); + readResourceFileAsJsonArray("choicesElements.json").stream().map(JsonValue::asJsonObject).forEach(o -> { System.out.println(o); - Object result = tester.test(o); + Object result = this.wrapAndTest(handler, o, weatherDescription, clazz); Assertions.assertNotNull(result); }); } - private JsonArray readResourceFileAsJsonArray(final String path) { + public Object 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(); 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..f7ad113 --- /dev/null +++ b/src/test/java/io/elastic/soap/jackson/XmlElementsChoice.java @@ -0,0 +1,5 @@ +package io.elastic.soap.jackson; + +public class XmlElementsChoice { + +} diff --git a/src/test/resources/choice.wsdl b/src/test/resources/choice.wsdl deleted file mode 100644 index 02e7fc9..0000000 --- a/src/test/resources/choice.wsdl +++ /dev/null @@ -1,348 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Gets Information for each WeatherID - - - - - Allows you to get your City Forecast Over the Next 7 Days, which is updated hourly. U.S. Only - - - - - Allows you to get your City's Weather, which is updated hourly. U.S. Only - - - - - - - Gets Information for each WeatherID - - - - - Allows you to get your City Forecast Over the Next 7 Days, which is updated hourly. U.S. Only - - - - - Allows you to get your City's Weather, which is updated hourly. U.S. Only - - - - - - - Gets Information for each WeatherID - - - - - Allows you to get your City Forecast Over the Next 7 Days, which is updated hourly. U.S. Only - - - - - Allows you to get your City's Weather, which is updated hourly. U.S. Only - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/resources/choices.json b/src/test/resources/choices.json deleted file mode 100644 index 0d46457..0000000 --- a/src/test/resources/choices.json +++ /dev/null @@ -1,61 +0,0 @@ -[ - { - "WeatherDescription": { - "responseURLOrOBSResultOrOBSResult1": ["1", 2] - } - }, - { - "WeatherDescription": { - "responseURLOrOBSResultOrOBSResult1": ["1", "2"] - } - }, - { - "WeatherDescription": { - "responseURLOrOBSResultOrOBSResult1": [1, 2] - } - }, - { - "WeatherDescription": { - "responseURLOrOBSResultOrOBSResult1": ["1", "2"] - } - }, - { - "WeatherDescription": { - "responseURLOrOBSResultOrOBSResult1": ["1", "2"] - } - }, - { - "WeatherDescription": { - "ResponseURL": [ - "1", - "3" - ] - } - }, - { - "WeatherDescription": { - "ResponseURL": ["1", "2"] - } - }, - { - "WeatherDescription": { - "OBSResult": ["1", "2"] - } - }, - { - "WeatherDescription": { - "ResponseURL": "1" - } - }, - { - "WeatherDescription": { - "OBSResult": "4" - } - }, - { - "WeatherDescription": { - "ResponseURL": "1", - "OBSResult": 1 - } - } -] \ No newline at end of file diff --git a/src/test/resources/choicesElements.json b/src/test/resources/choicesElements.json new file mode 100644 index 0000000..2f336f0 --- /dev/null +++ b/src/test/resources/choicesElements.json @@ -0,0 +1,104 @@ +[ + { + "WeatherDescription": { + "intFieldOrStringFieldOrComplexType": [ + "1", + "2" + ] + } + }, + { + "WeatherDescription": { + "intFieldOrStringFieldOrComplexType": [ + 1, + 2 + ] + } + }, + { + "WeatherDescription": { + "intFieldOrStringFieldOrComplexType": [ + { + "Success": true, + "ResponseText": "Test", + "City": "Test" + }, + { + "Success": false, + "ResponseText": "Test2", + "City": "Test2" + } + ] + } + }, + { + "WeatherDescription": { + "intFieldOrStringFieldOrComplexType": "1" + } + }, + { + "WeatherDescription": { + "intFieldOrStringFieldOrComplexType": 2 + } + }, + { + "WeatherDescription": { + "intFieldOrStringFieldOrComplexType": { + "Success": true, + "ResponseText": "Test", + "City": "Test" + } + } + }, + { + "WeatherDescription": { + "StringField": [ + "1", + "3" + ] + } + }, + { + "WeatherDescription": { + "IntField": [ + 1, + 2 + ] + } + }, + { + "WeatherDescription": { + "ComplexType": [ + { + "Success": true, + "ResponseText": "Test", + "City": "Test" + }, + { + "Success": false, + "ResponseText": "Test2", + "City": "Test2" + } + ] + } + }, + { + "WeatherDescription": { + "StringField": "4" + } + }, + { + "WeatherDescription": { + "IntField": 1 + } + }, + { + "WeatherDescription": { + "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..2f336f0 --- /dev/null +++ b/src/test/resources/choicesRefs.json @@ -0,0 +1,104 @@ +[ + { + "WeatherDescription": { + "intFieldOrStringFieldOrComplexType": [ + "1", + "2" + ] + } + }, + { + "WeatherDescription": { + "intFieldOrStringFieldOrComplexType": [ + 1, + 2 + ] + } + }, + { + "WeatherDescription": { + "intFieldOrStringFieldOrComplexType": [ + { + "Success": true, + "ResponseText": "Test", + "City": "Test" + }, + { + "Success": false, + "ResponseText": "Test2", + "City": "Test2" + } + ] + } + }, + { + "WeatherDescription": { + "intFieldOrStringFieldOrComplexType": "1" + } + }, + { + "WeatherDescription": { + "intFieldOrStringFieldOrComplexType": 2 + } + }, + { + "WeatherDescription": { + "intFieldOrStringFieldOrComplexType": { + "Success": true, + "ResponseText": "Test", + "City": "Test" + } + } + }, + { + "WeatherDescription": { + "StringField": [ + "1", + "3" + ] + } + }, + { + "WeatherDescription": { + "IntField": [ + 1, + 2 + ] + } + }, + { + "WeatherDescription": { + "ComplexType": [ + { + "Success": true, + "ResponseText": "Test", + "City": "Test" + }, + { + "Success": false, + "ResponseText": "Test2", + "City": "Test2" + } + ] + } + }, + { + "WeatherDescription": { + "StringField": "4" + } + }, + { + "WeatherDescription": { + "IntField": 1 + } + }, + { + "WeatherDescription": { + "ComplexType": { + "Success": true, + "ResponseText": "Test", + "City": "Test" + } + } + } +] \ No newline at end of file From e6d84b0a03bd2f72fd52c496a6c1d8c9d4598382 Mon Sep 17 00:00:00 2001 From: uaArsen Date: Fri, 29 Nov 2019 17:12:07 +0200 Subject: [PATCH 4/6] Update strcture --- .../jackson/AbstractChoiceDeserializer.java | 54 ++++++-- .../XMLElementRefsChoiceDeserializer.java | 10 +- .../XMLElementsChoiceDeserializer.java | 11 +- .../soap/jackson/XMLElementsIntrospector.java | 21 ++-- .../java/io/elastic/soap/utils/Utils.java | 1 + .../soap/jackson/ChoiceMetadataTest.java | 85 +++---------- .../io/elastic/soap/jackson/ComplexType.java | 118 ++++++++++++++++++ .../soap/jackson/XmlElementRefsChoice.java | 87 +++++++++++++ .../soap/jackson/XmlElementsChoice.java | 73 +++++++++++ src/test/resources/choicesElements.json | 24 ++-- src/test/resources/choicesRefs.json | 40 +++--- 11 files changed, 394 insertions(+), 130 deletions(-) create mode 100644 src/test/java/io/elastic/soap/jackson/ComplexType.java create mode 100644 src/test/java/io/elastic/soap/jackson/XmlElementRefsChoice.java diff --git a/src/main/java/io/elastic/soap/jackson/AbstractChoiceDeserializer.java b/src/main/java/io/elastic/soap/jackson/AbstractChoiceDeserializer.java index b17b189..1ca695c 100644 --- a/src/main/java/io/elastic/soap/jackson/AbstractChoiceDeserializer.java +++ b/src/main/java/io/elastic/soap/jackson/AbstractChoiceDeserializer.java @@ -13,18 +13,30 @@ import java.util.stream.Collectors; import javax.xml.bind.JAXBElement; -public abstract class AbstractJaxbDeserializer extends JsonDeserializer { +public abstract class AbstractChoiceDeserializer extends JsonDeserializer { protected final Class rawType; - protected final JavaType type; + protected final JavaType javaType; protected final ObjectMapper mapper; - protected AbstractJaxbDeserializer(JavaType type) { - this.type = type; - this.rawType = type.getRawClass(); + 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); @@ -32,6 +44,9 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx return this.mapper.convertValue(targetNode, this.rawType); } + /** + * @return List of possible types of choice element. + */ public abstract List getPossibleTypes(); /** @@ -52,7 +67,7 @@ public void nodeOneOfPossibleTypes(final JsonNode node, final List possib break; } } - if (!result && node.isArray() && (this.type.isArrayType() || this.type.isCollectionLikeType())) { + if (!result && isNodeAndRawTypeArray(node)) { result = handleArrayNode(node, possibleTypes); } if (!result) { @@ -61,10 +76,17 @@ public void nodeOneOfPossibleTypes(final JsonNode node, final List possib } /** - * Checks each item of node over provided possible types. + * @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 + * @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; @@ -78,6 +100,12 @@ public boolean handleArrayNode(JsonNode arrayNode, List possibleTypes) { 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)) { @@ -88,6 +116,12 @@ private Class findNodeType(JsonNode node, List 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); @@ -98,8 +132,8 @@ public boolean nodeCanBeConvertedToType(final JsonNode node, Class type) { } public String constructExceptionString(final JsonNode value, final List possibleTypes) { - StringBuilder bd = new StringBuilder("Failed to convert value: "); - bd.append(value.toPrettyString()).append("\n to one of: "); + StringBuilder bd = new StringBuilder("Failed to convert choice value: "); + bd.append(value.toPrettyString()).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/XMLElementRefsChoiceDeserializer.java b/src/main/java/io/elastic/soap/jackson/XMLElementRefsChoiceDeserializer.java index bec77e4..a2dc126 100644 --- a/src/main/java/io/elastic/soap/jackson/XMLElementRefsChoiceDeserializer.java +++ b/src/main/java/io/elastic/soap/jackson/XMLElementRefsChoiceDeserializer.java @@ -4,22 +4,24 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; -import javax.xml.bind.JAXBElement; import javax.xml.bind.annotation.XmlElementRef; import javax.xml.bind.annotation.XmlElementRefs; -public class XMLElementRefsDeserializer extends AbstractJaxbDeserializer { +public class XMLElementRefsChoiceDeserializer extends AbstractChoiceDeserializer { private final XmlElementRefs annotation; - public XMLElementRefsDeserializer(final JavaType type, 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).collect(Collectors.toList()); + 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 index 0622fc3..ddd7bfa 100644 --- a/src/main/java/io/elastic/soap/jackson/XMLElementsChoiceDeserializer.java +++ b/src/main/java/io/elastic/soap/jackson/XMLElementsChoiceDeserializer.java @@ -1,26 +1,27 @@ package io.elastic.soap.jackson; import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.JsonNode; 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 XMLElementsDeserializer extends AbstractJaxbDeserializer { +public class XMLElementsChoiceDeserializer extends AbstractChoiceDeserializer { private final XmlElements annotation; - public XMLElementsDeserializer(final JavaType type, 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).collect(Collectors.toList()); + 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 index ffc6074..d964049 100644 --- a/src/main/java/io/elastic/soap/jackson/XMLElementsIntrospector.java +++ b/src/main/java/io/elastic/soap/jackson/XMLElementsIntrospector.java @@ -1,4 +1,4 @@ -package io.elastic.soap.utils; +package io.elastic.soap.jackson; import com.fasterxml.jackson.databind.PropertyName; import com.fasterxml.jackson.databind.introspect.Annotated; @@ -9,12 +9,16 @@ import java.util.Optional; 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)) { @@ -32,17 +36,18 @@ public List findPropertyAliases(Annotated a) { 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)) { - List names = getXMLElementsNames(a).stream().map(PropertyName::getSimpleName).collect(Collectors.toList()); - names.add(a.getName()); - return new XMLElementRefDeserializer(a.getType(), a.getAnnotation(XmlElementRefs.class)); + return new XMLElementRefsChoiceDeserializer(a.getType(), a.getAnnotation(XmlElementRefs.class)); } if (a.hasAnnotation(XmlElements.class)) { - List names = getXMLElementsNames(a).stream().map(PropertyName::getSimpleName).collect(Collectors.toList()); - names.add(a.getName()); - return new XMLElementsDeserializer(a.getType(), a.getAnnotation(XmlElementRefs.class)); + return new XMLElementsChoiceDeserializer(a.getType(), a.getAnnotation(XmlElements.class)); } return super.findDeserializer(a); } diff --git a/src/main/java/io/elastic/soap/utils/Utils.java b/src/main/java/io/elastic/soap/utils/Utils.java index 505f757..7966f3c 100644 --- a/src/main/java/io/elastic/soap/utils/Utils.java +++ b/src/main/java/io/elastic/soap/utils/Utils.java @@ -14,6 +14,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; diff --git a/src/test/java/io/elastic/soap/jackson/ChoiceMetadataTest.java b/src/test/java/io/elastic/soap/jackson/ChoiceMetadataTest.java index 6f3e99b..526c965 100644 --- a/src/test/java/io/elastic/soap/jackson/ChoiceMetadataTest.java +++ b/src/test/java/io/elastic/soap/jackson/ChoiceMetadataTest.java @@ -1,93 +1,36 @@ -package io.elastic.soap.providers; +package io.elastic.soap.jackson; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; - -import com.predic8.wsdl.Definitions; -import com.predic8.wsdl.WSDLParser; -import io.elastic.soap.AppConstants; -import io.elastic.soap.compilers.JaxbCompiler; import io.elastic.soap.handlers.RequestHandler; -import io.elastic.soap.services.WSDLService; -import io.elastic.soap.services.impls.HttpWSDLService; -import java.io.File; -import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.util.Arrays; import javax.json.Json; import javax.json.JsonArray; import javax.json.JsonObject; import javax.json.JsonReader; import javax.json.JsonValue; -import org.apache.commons.io.FileUtils; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; public class ChoiceMetadataTest { - private static BodyMetaProvider provider; - private static JsonObject config; - private final static String WSDL_URL = "src/test/resources/choices.wsdl"; - private static String[] arrayOfDirsToDelete = {"src/com", "src/io"}; - - @BeforeAll - public static void beforeAll() throws Throwable { - Definitions definitions = getDefinitions(WSDL_URL); - config = Json.createObjectBuilder() - .add(AppConstants.BINDING_CONFIG_NAME, "SOAP11") - .add(AppConstants.OPERATION_CONFIG_NAME, "GetXmlElementRefsChoice") - .add(AppConstants.WSDL_CONFIG_NAME, "http://elatic.io/test/") - .add("auth", - Json.createObjectBuilder().add("type", "No Auth") - .add("basic", Json.createObjectBuilder().add("username", "") - .add("password", "") - .build()) - ) - .build(); - - provider = new BodyMetaProvider(); - WSDLService service = spy(new HttpWSDLService()); - provider.setWsdlService(service); - doReturn(definitions).when(service).getWSDL(any(JsonObject.class)); - JaxbCompiler.generateAndLoadJaxbStructure(WSDL_URL); - JaxbCompiler.putToCache("http://elatic.io/test/", AppConstants.GENERATED_RESOURCES_DIR); - } - - - @AfterAll - public static void cleanup() throws IOException { - for (final String dirName : Arrays.asList(arrayOfDirsToDelete)) { - final File dir = new File(dirName); - if (dir.exists()) { - FileUtils.deleteDirectory(dir); - } - } - - FileUtils.cleanDirectory(new File(AppConstants.GENERATED_RESOURCES_DIR)); - } - - public static Definitions getDefinitions(final String wsdlPath) { - final WSDLParser parser = new WSDLParser(); - return parser.parse(wsdlPath); - } - @Test - public void choiceMetadataGeneration() { - final JsonObject object = provider.getMetaModel(config); - Assertions.assertNotNull(object.get("in")); - Assertions.assertNotNull(object.get("out")); + public void serializeClassWithXmlElementsAnnotation() throws ClassNotFoundException { + final RequestHandler handler = new RequestHandler(); + final String weatherDescription = "XmlElementsChoice"; + final Class clazz = XmlElementsChoice.class; + readResourceFileAsJsonArray("choicesElements.json").stream().map(JsonValue::asJsonObject).forEach(o -> { + System.out.println(o); + Object result = this.wrapAndTest(handler, o, weatherDescription, clazz); + Assertions.assertNotNull(result); + }); } @Test - public void serializeClassWithXmlElementsAnnotation() throws ClassNotFoundException { + public void serializeClassWithXmlElementRefssAnnotation() throws ClassNotFoundException { final RequestHandler handler = new RequestHandler(); - final String weatherDescription = "WeatherDescription"; - final Class clazz = Class.forName("io.elastic.test.XmlElementsChoice"); - readResourceFileAsJsonArray("choicesElements.json").stream().map(JsonValue::asJsonObject).forEach(o -> { + final String weatherDescription = "XmlElementRefsChoice"; + final Class clazz = XmlElementRefsChoice.class; + readResourceFileAsJsonArray("choicesRefs.json").stream().map(JsonValue::asJsonObject).forEach(o -> { System.out.println(o); Object result = this.wrapAndTest(handler, o, weatherDescription, clazz); Assertions.assertNotNull(result); 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 index f7ad113..32a10bb 100644 --- a/src/test/java/io/elastic/soap/jackson/XmlElementsChoice.java +++ b/src/test/java/io/elastic/soap/jackson/XmlElementsChoice.java @@ -1,5 +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 index 2f336f0..9edf65b 100644 --- a/src/test/resources/choicesElements.json +++ b/src/test/resources/choicesElements.json @@ -1,6 +1,6 @@ [ { - "WeatherDescription": { + "XmlElementsChoice": { "intFieldOrStringFieldOrComplexType": [ "1", "2" @@ -8,7 +8,7 @@ } }, { - "WeatherDescription": { + "XmlElementsChoice": { "intFieldOrStringFieldOrComplexType": [ 1, 2 @@ -16,7 +16,7 @@ } }, { - "WeatherDescription": { + "XmlElementsChoice": { "intFieldOrStringFieldOrComplexType": [ { "Success": true, @@ -32,17 +32,17 @@ } }, { - "WeatherDescription": { + "XmlElementsChoice": { "intFieldOrStringFieldOrComplexType": "1" } }, { - "WeatherDescription": { + "XmlElementsChoice": { "intFieldOrStringFieldOrComplexType": 2 } }, { - "WeatherDescription": { + "XmlElementsChoice": { "intFieldOrStringFieldOrComplexType": { "Success": true, "ResponseText": "Test", @@ -51,7 +51,7 @@ } }, { - "WeatherDescription": { + "XmlElementsChoice": { "StringField": [ "1", "3" @@ -59,7 +59,7 @@ } }, { - "WeatherDescription": { + "XmlElementsChoice": { "IntField": [ 1, 2 @@ -67,7 +67,7 @@ } }, { - "WeatherDescription": { + "XmlElementsChoice": { "ComplexType": [ { "Success": true, @@ -83,17 +83,17 @@ } }, { - "WeatherDescription": { + "XmlElementsChoice": { "StringField": "4" } }, { - "WeatherDescription": { + "XmlElementsChoice": { "IntField": 1 } }, { - "WeatherDescription": { + "XmlElementsChoice": { "ComplexType": { "Success": true, "ResponseText": "Test", diff --git a/src/test/resources/choicesRefs.json b/src/test/resources/choicesRefs.json index 2f336f0..c013f6c 100644 --- a/src/test/resources/choicesRefs.json +++ b/src/test/resources/choicesRefs.json @@ -1,23 +1,23 @@ [ { - "WeatherDescription": { - "intFieldOrStringFieldOrComplexType": [ + "XmlElementRefsChoice": { + "intFieldOrStringField1OrStringField2": [ "1", "2" ] } }, { - "WeatherDescription": { - "intFieldOrStringFieldOrComplexType": [ + "XmlElementRefsChoice": { + "intFieldOrStringField1OrStringField2": [ 1, 2 ] } }, { - "WeatherDescription": { - "intFieldOrStringFieldOrComplexType": [ + "XmlElementRefsChoice": { + "intFieldOrStringField1OrStringField2": [ { "Success": true, "ResponseText": "Test", @@ -32,18 +32,18 @@ } }, { - "WeatherDescription": { - "intFieldOrStringFieldOrComplexType": "1" + "XmlElementRefsChoice": { + "intFieldOrStringField1OrStringField2": "1" } }, { - "WeatherDescription": { - "intFieldOrStringFieldOrComplexType": 2 + "XmlElementRefsChoice": { + "intFieldOrStringField1OrStringField2": 2 } }, { - "WeatherDescription": { - "intFieldOrStringFieldOrComplexType": { + "XmlElementRefsChoice": { + "intFieldOrStringField1OrStringField2": { "Success": true, "ResponseText": "Test", "City": "Test" @@ -51,15 +51,15 @@ } }, { - "WeatherDescription": { - "StringField": [ + "XmlElementRefsChoice": { + "StringField1": [ "1", "3" ] } }, { - "WeatherDescription": { + "XmlElementRefsChoice": { "IntField": [ 1, 2 @@ -67,7 +67,7 @@ } }, { - "WeatherDescription": { + "XmlElementRefsChoice": { "ComplexType": [ { "Success": true, @@ -83,17 +83,17 @@ } }, { - "WeatherDescription": { - "StringField": "4" + "XmlElementRefsChoice": { + "StringField2": "4" } }, { - "WeatherDescription": { + "XmlElementRefsChoice": { "IntField": 1 } }, { - "WeatherDescription": { + "XmlElementRefsChoice": { "ComplexType": { "Success": true, "ResponseText": "Test", From cf59eeb42705c924494141b0e09365c7729b9538 Mon Sep 17 00:00:00 2001 From: uaArsen Date: Mon, 2 Dec 2019 14:39:55 +0200 Subject: [PATCH 5/6] Fix comments --- .../jackson/AbstractChoiceDeserializer.java | 4 +- .../JaxbElementsJsonValueConverter.java | 22 +++++ ... => XmlElementRefsChoiceDeserializer.java} | 5 +- ...ava => XmlElementsChoiceDeserializer.java} | 4 +- ...ctor.java => XmlElementsIntrospector.java} | 25 +++-- .../java/io/elastic/soap/utils/Utils.java | 9 +- .../soap/jackson/ChoiceMetadataTest.java | 95 ++++++++++--------- 7 files changed, 102 insertions(+), 62 deletions(-) create mode 100644 src/main/java/io/elastic/soap/jackson/JaxbElementsJsonValueConverter.java rename src/main/java/io/elastic/soap/jackson/{XMLElementRefsChoiceDeserializer.java => XmlElementRefsChoiceDeserializer.java} (83%) rename src/main/java/io/elastic/soap/jackson/{XMLElementsChoiceDeserializer.java => XmlElementsChoiceDeserializer.java} (83%) rename src/main/java/io/elastic/soap/jackson/{XMLElementsIntrospector.java => XmlElementsIntrospector.java} (74%) diff --git a/src/main/java/io/elastic/soap/jackson/AbstractChoiceDeserializer.java b/src/main/java/io/elastic/soap/jackson/AbstractChoiceDeserializer.java index 1ca695c..c72e7e2 100644 --- a/src/main/java/io/elastic/soap/jackson/AbstractChoiceDeserializer.java +++ b/src/main/java/io/elastic/soap/jackson/AbstractChoiceDeserializer.java @@ -132,8 +132,8 @@ public boolean nodeCanBeConvertedToType(final JsonNode node, Class type) { } public String constructExceptionString(final JsonNode value, final List possibleTypes) { - StringBuilder bd = new StringBuilder("Failed to convert choice value: "); - bd.append(value.toPrettyString()).append("to one of: "); + 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 similarity index 83% rename from src/main/java/io/elastic/soap/jackson/XMLElementRefsChoiceDeserializer.java rename to src/main/java/io/elastic/soap/jackson/XmlElementRefsChoiceDeserializer.java index a2dc126..0516140 100644 --- a/src/main/java/io/elastic/soap/jackson/XMLElementRefsChoiceDeserializer.java +++ b/src/main/java/io/elastic/soap/jackson/XmlElementRefsChoiceDeserializer.java @@ -7,11 +7,11 @@ import javax.xml.bind.annotation.XmlElementRef; import javax.xml.bind.annotation.XmlElementRefs; -public class XMLElementRefsChoiceDeserializer extends AbstractChoiceDeserializer { +public class XmlElementRefsChoiceDeserializer extends AbstractChoiceDeserializer { private final XmlElementRefs annotation; - public XMLElementRefsChoiceDeserializer(final JavaType type, final XmlElementRefs annotation) { + public XmlElementRefsChoiceDeserializer(final JavaType type, final XmlElementRefs annotation) { super(type); this.annotation = annotation; } @@ -24,4 +24,5 @@ public List getPossibleTypes() { .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 similarity index 83% rename from src/main/java/io/elastic/soap/jackson/XMLElementsChoiceDeserializer.java rename to src/main/java/io/elastic/soap/jackson/XmlElementsChoiceDeserializer.java index ddd7bfa..d463d52 100644 --- a/src/main/java/io/elastic/soap/jackson/XMLElementsChoiceDeserializer.java +++ b/src/main/java/io/elastic/soap/jackson/XmlElementsChoiceDeserializer.java @@ -7,12 +7,12 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElements; -public class XMLElementsChoiceDeserializer extends AbstractChoiceDeserializer { +public class XmlElementsChoiceDeserializer extends AbstractChoiceDeserializer { private final XmlElements annotation; - public XMLElementsChoiceDeserializer(final JavaType type, final XmlElements annotation) { + public XmlElementsChoiceDeserializer(final JavaType type, final XmlElements annotation) { super(type); this.annotation = annotation; } diff --git a/src/main/java/io/elastic/soap/jackson/XMLElementsIntrospector.java b/src/main/java/io/elastic/soap/jackson/XmlElementsIntrospector.java similarity index 74% rename from src/main/java/io/elastic/soap/jackson/XMLElementsIntrospector.java rename to src/main/java/io/elastic/soap/jackson/XmlElementsIntrospector.java index d964049..ebe75c2 100644 --- a/src/main/java/io/elastic/soap/jackson/XMLElementsIntrospector.java +++ b/src/main/java/io/elastic/soap/jackson/XmlElementsIntrospector.java @@ -3,16 +3,15 @@ import com.fasterxml.jackson.databind.PropertyName; import com.fasterxml.jackson.databind.introspect.Annotated; import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; + +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 { +public class XmlElementsIntrospector extends JacksonAnnotationIntrospector { /** * Finds alias names of field in annotations XmlElements and XmlElementRefs. @@ -23,7 +22,7 @@ public class XMLElementsIntrospector extends JacksonAnnotationIntrospector { 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); + final List names = getXmlElementsNames(a); result.addAll(names); return result; } @@ -44,15 +43,23 @@ public List findPropertyAliases(Annotated a) { @Override public Object findDeserializer(Annotated a) { if (a.hasAnnotation(XmlElementRefs.class)) { - return new XMLElementRefsChoiceDeserializer(a.getType(), a.getAnnotation(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 new XmlElementsChoiceDeserializer(a.getType(), a.getAnnotation(XmlElements.class)); } return super.findDeserializer(a); } - public List getXMLElementsNames(final Annotated 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()); } diff --git a/src/main/java/io/elastic/soap/utils/Utils.java b/src/main/java/io/elastic/soap/utils/Utils.java index 7966f3c..010bbee 100644 --- a/src/main/java/io/elastic/soap/utils/Utils.java +++ b/src/main/java/io/elastic/soap/utils/Utils.java @@ -4,6 +4,7 @@ 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; @@ -14,7 +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.jackson.XmlElementsIntrospector; import io.elastic.soap.services.SoapCallService; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -64,14 +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.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 index 526c965..628d673 100644 --- a/src/test/java/io/elastic/soap/jackson/ChoiceMetadataTest.java +++ b/src/test/java/io/elastic/soap/jackson/ChoiceMetadataTest.java @@ -1,55 +1,64 @@ package io.elastic.soap.jackson; +import com.fasterxml.jackson.databind.ObjectMapper; import io.elastic.soap.handlers.RequestHandler; -import java.io.InputStream; -import java.io.InputStreamReader; -import javax.json.Json; -import javax.json.JsonArray; -import javax.json.JsonObject; -import javax.json.JsonReader; -import javax.json.JsonValue; +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"; - final Class clazz = XmlElementsChoice.class; - readResourceFileAsJsonArray("choicesElements.json").stream().map(JsonValue::asJsonObject).forEach(o -> { - System.out.println(o); - Object result = this.wrapAndTest(handler, o, weatherDescription, clazz); - Assertions.assertNotNull(result); - }); - } + @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 serializeClassWithXmlElementRefssAnnotation() throws ClassNotFoundException { - final RequestHandler handler = new RequestHandler(); - final String weatherDescription = "XmlElementRefsChoice"; - final Class clazz = XmlElementRefsChoice.class; - readResourceFileAsJsonArray("choicesRefs.json").stream().map(JsonValue::asJsonObject).forEach(o -> { - System.out.println(o); - Object result = this.wrapAndTest(handler, o, weatherDescription, clazz); - Assertions.assertNotNull(result); - }); - } + @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 Object 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 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(); + } } From 1ad8a4840dc8cb389caf8d0aeacb4bfd9bcabb73 Mon Sep 17 00:00:00 2001 From: uaArsen Date: Mon, 2 Dec 2019 15:12:20 +0200 Subject: [PATCH 6/6] Remove unused codition --- .../io/elastic/soap/jackson/XmlElementsIntrospector.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/io/elastic/soap/jackson/XmlElementsIntrospector.java b/src/main/java/io/elastic/soap/jackson/XmlElementsIntrospector.java index ebe75c2..2c00aac 100644 --- a/src/main/java/io/elastic/soap/jackson/XmlElementsIntrospector.java +++ b/src/main/java/io/elastic/soap/jackson/XmlElementsIntrospector.java @@ -26,12 +26,6 @@ public List findPropertyAliases(Annotated a) { result.addAll(names); return result; } - if (a.hasAnnotation(XmlElement.class)) { - final List result = Optional.ofNullable(super.findPropertyAliases(a)).orElse(new ArrayList<>()); - XmlElement element = a.getAnnotation(XmlElement.class); - result.add(new PropertyName(element.name())); - return result; - } return super.findPropertyAliases(a); }