-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Choice fix #5
Open
uaArsen
wants to merge
6
commits into
master
Choose a base branch
from
choice-fix
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Choice fix #5
Changes from 4 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
140 changes: 140 additions & 0 deletions
140
src/main/java/io/elastic/soap/jackson/AbstractChoiceDeserializer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Object> or List<Serializble>. 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<Class> 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<Class> 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<Class> 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<Class> 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<Class> possibleTypes) { | ||
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(); | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
src/main/java/io/elastic/soap/jackson/XMLElementRefsChoiceDeserializer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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.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<Class> getPossibleTypes() { | ||
return Arrays.stream(this.annotation.value()) | ||
.map(XmlElementRef::type) | ||
.filter(c -> !c.equals(XmlElementRef.DEFAULT.class)) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
} |
27 changes: 27 additions & 0 deletions
27
src/main/java/io/elastic/soap/jackson/XMLElementsChoiceDeserializer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Class> getPossibleTypes() { | ||
return Arrays.stream(this.annotation.value()) | ||
.map(XmlElement::type) | ||
.filter(c -> !c.equals(XmlElement.DEFAULT.class)) | ||
.collect(Collectors.toList()); | ||
} | ||
} |
61 changes: 61 additions & 0 deletions
61
src/main/java/io/elastic/soap/jackson/XMLElementsIntrospector.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
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.ArrayList; | ||
import java.util.Arrays; | ||
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; | ||
|
||
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<PropertyName> findPropertyAliases(Annotated a) { | ||
if (a.hasAnnotation(XmlElements.class) || a.hasAnnotation(XmlElementRefs.class)) { | ||
final List<PropertyName> result = Optional.ofNullable(super.findPropertyAliases(a)).orElse(new ArrayList<>()); | ||
final List<PropertyName> names = getXMLElementsNames(a); | ||
result.addAll(names); | ||
return result; | ||
} | ||
if (a.hasAnnotation(XmlElement.class)) { | ||
final List<PropertyName> 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); | ||
} | ||
|
||
/** | ||
* 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); | ||
} | ||
|
||
public List<PropertyName> 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()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
src/test/java/io/elastic/soap/jackson/ChoiceMetadataTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package io.elastic.soap.jackson; | ||
|
||
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 org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.Test; | ||
|
||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Assert |
||
}); | ||
} | ||
|
||
@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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Assert |
||
}); | ||
} | ||
|
||
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; | ||
} | ||
|
||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's possible to wrap this two condition blocks into one
if (a.hasAnnotation(XmlElement.class)) {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No