From 53567bdd71a6c9a3d3ec69b097acea2aca31a6ef Mon Sep 17 00:00:00 2001 From: Ethan Trepka Date: Mon, 20 Jul 2020 16:49:58 -0500 Subject: [PATCH] Add disableFromNode to NodeMapper --- .../model/node/DefaultNodeDeserializers.java | 63 ++++++++++--------- .../amazon/smithy/model/node/NodeMapper.java | 41 +++++++++++- .../smithy/model/node/NodeMapperTest.java | 25 ++++++++ 3 files changed, 97 insertions(+), 32 deletions(-) diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/node/DefaultNodeDeserializers.java b/smithy-model/src/main/java/software/amazon/smithy/model/node/DefaultNodeDeserializers.java index 9e4bc59a7a0..008c5551eba 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/node/DefaultNodeDeserializers.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/node/DefaultNodeDeserializers.java @@ -59,14 +59,14 @@ final class DefaultNodeDeserializers { // Deserialize an exact type if it matches (i.e., the setter expects a Node value). - private static final ObjectCreatorFactory EXACT_CREATOR_FACTORY = (nodeType, targetType) -> { + private static final ObjectCreatorFactory EXACT_CREATOR_FACTORY = (nodeType, targetType, nodeMapper) -> { return Node.class.isAssignableFrom(targetType) && targetType.isAssignableFrom(nodeType.getNodeClass()) ? (node, target, param, pointer, mapper) -> node : null; }; // Creates booleans from BooleanNodes. - private static final ObjectCreatorFactory BOOLEAN_CREATOR_FACTORY = (nodeType, targetType) -> { + private static final ObjectCreatorFactory BOOLEAN_CREATOR_FACTORY = (nodeType, targetType, nodeMapper) -> { if (nodeType == NodeType.BOOLEAN) { if (targetType == Boolean.class || targetType == boolean.class || targetType == Object.class) { return (node, target, param, pointer, mapper) -> node.expectBooleanNode().getValue(); @@ -76,7 +76,7 @@ final class DefaultNodeDeserializers { }; // Null nodes always return null values. - private static final ObjectCreatorFactory NULL_CREATOR = (nodeType, targetType) -> { + private static final ObjectCreatorFactory NULL_CREATOR = (nodeType, targetType, nodeMapper) -> { if (nodeType == NodeType.NULL) { return (node, target, param, pointer, mapper) -> null; } @@ -84,7 +84,7 @@ final class DefaultNodeDeserializers { }; // String nodes can create java.land.String or a Smithy ShapeId. - private static final ObjectCreatorFactory STRING_CREATOR = (nodeType, into) -> { + private static final ObjectCreatorFactory STRING_CREATOR = (nodeType, into, nodeMapper) -> { if (nodeType == NodeType.STRING) { if (into == String.class || into == Object.class) { return (node, target, param, pointer, mapper) -> node.expectStringNode().getValue(); @@ -116,7 +116,7 @@ final class DefaultNodeDeserializers { } // Creates numbers from NumberNodes. - private static final ObjectCreatorFactory NUMBER_CREATOR = (nodeType, targetType) -> { + private static final ObjectCreatorFactory NUMBER_CREATOR = (nodeType, targetType, nodeMapper) -> { if (nodeType == NodeType.NUMBER) { if (NUMBER_MAPPERS.containsKey(targetType)) { return (node, target, param, pointer, mapper) -> { @@ -135,7 +135,7 @@ private interface ReflectiveSupplier { // Deserialize an ArrayNode into a Collection. private static final ObjectCreatorFactory COLLECTION_CREATOR = new ObjectCreatorFactory() { @Override - public NodeMapper.ObjectCreator getCreator(NodeType nodeType, Class target) { + public NodeMapper.ObjectCreator getCreator(NodeType nodeType, Class target, NodeMapper nodeMapper) { if (nodeType != NodeType.ARRAY) { return null; } @@ -209,7 +209,7 @@ private static String getCauseMessage(Throwable e) { // Deserialize an ObjectNode into a Map. private static final ObjectCreatorFactory MAP_CREATOR = new ObjectCreatorFactory() { @Override - public NodeMapper.ObjectCreator getCreator(NodeType nodeType, Class target) { + public NodeMapper.ObjectCreator getCreator(NodeType nodeType, Class target, NodeMapper nodeMapper) { if (nodeType != NodeType.OBJECT) { return null; } @@ -269,21 +269,24 @@ private ReflectiveSupplier> createSupplierFromReflection(Cla }; // Creates an object from any type of Node using the #fromNode factory method. - private static final ObjectCreatorFactory FROM_NODE_CREATOR = (nodeType, target) -> { - for (Method method : target.getMethods()) { - if ((method.getName().equals("fromNode")) - && target.isAssignableFrom(method.getReturnType()) - && method.getParameters().length == 1 - && Node.class.isAssignableFrom(method.getParameters()[0].getType()) - && Modifier.isStatic(method.getModifiers())) { - return (node, targetType, paramType, pointer, mapper) -> { - try { - return method.invoke(null, node); - } catch (ReflectiveOperationException e) { - String message = "Unable to deserialize Node using fromNode method: " + getCauseMessage(e); - throw NodeDeserializationException.fromReflectiveContext(targetType, pointer, node, e, message); - } - }; + private static final ObjectCreatorFactory FROM_NODE_CREATOR = (nodeType, target, nodeMapper) -> { + if (!nodeMapper.getDisableFromNode().contains(target)) { + for (Method method : target.getMethods()) { + if ((method.getName().equals("fromNode")) + && target.isAssignableFrom(method.getReturnType()) + && method.getParameters().length == 1 + && Node.class.isAssignableFrom(method.getParameters()[0].getType()) + && Modifier.isStatic(method.getModifiers())) { + return (node, targetType, paramType, pointer, mapper) -> { + try { + return method.invoke(null, node); + } catch (ReflectiveOperationException e) { + String message = "Unable to deserialize Node using fromNode method: " + getCauseMessage(e); + throw NodeDeserializationException + .fromReflectiveContext(targetType, pointer, node, e, message); + } + }; + } } } return null; @@ -439,7 +442,7 @@ private static Class resolveClassFromType(Type type) { // Creates an object from any type of Node using the #builder factory method. @SuppressWarnings("unchecked") - private static final ObjectCreatorFactory FROM_BUILDER_CREATOR = (nodeType, target) -> { + private static final ObjectCreatorFactory FROM_BUILDER_CREATOR = (nodeType, target, nodeMapper) -> { if (nodeType != NodeType.OBJECT) { return null; } @@ -475,7 +478,7 @@ private static void applySourceLocation(Object object, FromSourceLocation source } // Attempts to create a Bean style POJO using a zero-value constructor. - private static final ObjectCreatorFactory BEAN_CREATOR = (nodeType, target) -> { + private static final ObjectCreatorFactory BEAN_CREATOR = (nodeType, target, nodeMapper) -> { if (nodeType != NodeType.OBJECT) { return null; } @@ -511,7 +514,7 @@ private static void applySourceLocation(Object object, FromSourceLocation source // calling toString on the variant matches the given string. // Mimic's Jackson's behavior when using READ_ENUMS_USING_TO_STRING // See https://github.com/FasterXML/jackson-databind/wiki/Deserialization-Features - private static final ObjectCreatorFactory ENUM_CREATOR = (nodeType, target) -> { + private static final ObjectCreatorFactory ENUM_CREATOR = (nodeType, target, nodeMapper) -> { if (nodeType != NodeType.STRING || !Enum.class.isAssignableFrom(target)) { return null; } @@ -550,7 +553,7 @@ private interface FromStringClassFactory { File.class, File::new ); - private static final ObjectCreatorFactory FROM_STRING = (nodeType, target) -> { + private static final ObjectCreatorFactory FROM_STRING = (nodeType, target, nodeMapper) -> { if (nodeType != NodeType.STRING || !FROM_STRING_CLASSES.containsKey(target)) { return null; } @@ -589,9 +592,9 @@ private interface FromStringClassFactory { BEAN_CREATOR ); - static final ObjectCreatorFactory DEFAULT_CHAIN = (nodeType, target) -> { + static final ObjectCreatorFactory DEFAULT_CHAIN = (nodeType, target, nodeMapper) -> { for (ObjectCreatorFactory factory : DEFAULT_FACTORIES) { - NodeMapper.ObjectCreator result = factory.getCreator(nodeType, target); + NodeMapper.ObjectCreator result = factory.getCreator(nodeType, target, nodeMapper); if (result != null) { return result; } @@ -602,9 +605,9 @@ private interface FromStringClassFactory { // Creates an ObjectCreatorFactory that caches the result of finding ObjectCreators. private static ObjectCreatorFactory cachedCreator(ObjectCreatorFactory delegate) { ConcurrentMap, NodeMapper.ObjectCreator> cache = new ConcurrentHashMap<>(); - return (nodeType, target) -> { + return (nodeType, target, nodeMapper) -> { return cache.computeIfAbsent(Pair.of(nodeType, target), pair -> { - return delegate.getCreator(pair.left, pair.right); + return delegate.getCreator(pair.left, pair.right, nodeMapper); }); }; } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/node/NodeMapper.java b/smithy-model/src/main/java/software/amazon/smithy/model/node/NodeMapper.java index a8e9904fc01..3d0977c8e57 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/node/NodeMapper.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/node/NodeMapper.java @@ -164,15 +164,17 @@ interface ObjectCreatorFactory { * * @param nodeType Node type being converted. * @param target The class to create from the Node. + * @param nodeMapper The NodeMapper being used to call the ObjectCreator. * @return Returns the {@code ObjectCreator} or {@code null} if the factory cannot handle the given arguments. * @throws NodeDeserializationException when unable to create a factory. */ - ObjectCreator getCreator(NodeType nodeType, Class target); + ObjectCreator getCreator(NodeType nodeType, Class target, NodeMapper nodeMapper); } private static final Logger LOGGER = Logger.getLogger(NodeMapper.class.getName()); private WhenMissing whenMissing = WhenMissing.WARN; private final Set disableToNode = new HashSet<>(); + private final Set disableFromNode = new HashSet<>(); private boolean serializeNullValues = false; private boolean omitEmptyValues; @@ -246,6 +248,41 @@ public Set getDisableToNode() { return disableToNode; } + /** + * Disables the use of {@code fromNode} method for a specific class + * when deserializing the class. + * + *

This method disables a specific concrete class and does not + * disable subclasses or implementations of an interface. + * + *

This is useful when using the NodeMapper inside of a {@code fromNode} + * implementation. + * + * @param type Class to disable the {@code fromNode} method deserialization for. + */ + public void disableFromNodeForClass(Class type) { + disableFromNode.add(type); + } + + /** + * Enables the use of the {@code FromNode} method for a specific class + * when deserializing the class. + * + * @param type Class to enable the {@code fromNode} method deserialization for. + */ + public void enableFromNodeForClass(Class type) { + disableFromNode.remove(type); + } + + /** + * Gets the set of classes where {@code fromNode} is disabled. + * + * @return Returns the disabled classes. + */ + public Set getDisableFromNode() { + return disableFromNode; + } + /** * Gets whether or not empty arrays and empty objects are omitted from * serialized POJOs. @@ -496,7 +533,7 @@ T deserializeNext( Objects.requireNonNull(mapper, "Deserialization mapper cannot be null"); try { - ObjectCreator creator = creatorFactory.getCreator(value.getType(), into); + ObjectCreator creator = creatorFactory.getCreator(value.getType(), into, this); if (creator == null) { throw createError(into, pointer, value, null, null); } diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/node/NodeMapperTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/node/NodeMapperTest.java index 0ff418298a1..66463f98606 100644 --- a/smithy-model/src/test/java/software/amazon/smithy/model/node/NodeMapperTest.java +++ b/smithy-model/src/test/java/software/amazon/smithy/model/node/NodeMapperTest.java @@ -411,6 +411,31 @@ public String getFoo() { } } + @Test + public void canDisableFromNodeInsideOfClass() { + ObjectNode objectNode = ObjectNode.objectNodeBuilder().withMember("foo", "baz").build(); + NodeMapper mapper = new NodeMapper(); + mapper.disableFromNodeForClass(DisabledFromNode.class); + DisabledFromNode result = mapper.deserialize(objectNode, DisabledFromNode.class); + assertThat(result.getFoo(), equalTo("baz")); + } + + public static final class DisabledFromNode { + private String foo; + + public static DisabledFromNode fromNode() { + throw new RuntimeException("Was not meant to run!"); + } + + public String getFoo() { + return foo; + } + + public void setFoo(String foo) { + this.foo = foo; + } + } + @Test public void deserializesWithFromNodeFactoryAndUnknownPropertiesWithWarning() { Node baz = Node.parse("{\"foo\": \"hi\", \"baz\": 10, \"inner\": {\"inner\": {\"noSetter!\": \"inn!\"}}}");