Skip to content
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

Disable NodeMapper fromNode during deserialization #505

Merged
merged 1 commit into from
Jul 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -76,15 +76,15 @@ 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;
}
return null;
};

// 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();
Expand Down Expand Up @@ -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) -> {
Expand All @@ -135,7 +135,7 @@ private interface ReflectiveSupplier<T> {
// 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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -269,21 +269,24 @@ private ReflectiveSupplier<Map<String, Object>> 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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand All @@ -602,9 +605,9 @@ private interface FromStringClassFactory {
// Creates an ObjectCreatorFactory that caches the result of finding ObjectCreators.
private static ObjectCreatorFactory cachedCreator(ObjectCreatorFactory delegate) {
ConcurrentMap<Pair<NodeType, Class>, 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);
});
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Class> disableToNode = new HashSet<>();
private final Set<Class> disableFromNode = new HashSet<>();
private boolean serializeNullValues = false;
private boolean omitEmptyValues;

Expand Down Expand Up @@ -246,6 +248,41 @@ public Set<Class> getDisableToNode() {
return disableToNode;
}

/**
* Disables the use of {@code fromNode} method for a specific class
* when deserializing the class.
*
* <p>This method disables a specific concrete class and does not
* disable subclasses or implementations of an interface.
*
* <p>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<Class> getDisableFromNode() {
return disableFromNode;
}

/**
* Gets whether or not empty arrays and empty objects are omitted from
* serialized POJOs.
Expand Down Expand Up @@ -496,7 +533,7 @@ <T> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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!\"}}}");
Expand Down