Check for duplicate keys in YAML map nodes

Snake YAML allows for duplicate keys in map nodes. See
https://code.google.com/p/snakeyaml/issues/detail?id=199

This commit uses a dedicated Constructor extension that explicitly
checks for such duplicate keys.

Issue: SPR-12318
This commit is contained in:
Stephane Nicoll
2014-10-14 15:58:35 +02:00
parent 3056301015
commit 859e1e8003
4 changed files with 100 additions and 50 deletions

View File

@@ -17,6 +17,7 @@
package org.springframework.beans.factory.config;
import java.io.IOException;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -25,10 +26,14 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.parser.ParserException;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
@@ -126,7 +131,7 @@ public abstract class YamlProcessor {
* @param callback a callback to delegate to once matching documents are found
*/
protected void process(MatchCallback callback) {
Yaml yaml = new Yaml();
Yaml yaml = new Yaml(new StrictMapAppenderConstructor());
for (Resource resource : this.resources) {
boolean found = process(callback, yaml, resource);
if (this.resolutionMethod == ResolutionMethod.FIRST_FOUND && found) {
@@ -351,4 +356,44 @@ public abstract class YamlProcessor {
FIRST_FOUND
}
/**
* A specialized {@link Constructor} that checks for duplicate keys.
*/
private static class StrictMapAppenderConstructor extends Constructor {
public StrictMapAppenderConstructor() {
super();
}
@Override
protected Map<Object, Object> constructMapping(MappingNode node) {
try {
return super.constructMapping(node);
} catch (IllegalStateException e) {
throw new ParserException("while parsing MappingNode",
node.getStartMark(), e.getMessage(), node.getEndMark());
}
}
@Override
protected Map<Object, Object> createDefaultMap() {
final Map<Object, Object> delegate = super.createDefaultMap();
return new AbstractMap<Object, Object>() {
@Override
public Object put(Object key, Object value) {
if (delegate.containsKey(key)) {
throw new IllegalStateException("duplicate key: " + key);
}
return delegate.put(key, value);
}
@Override
public Set<Entry<Object, Object>> entrySet() {
return delegate.entrySet();
}
};
}
}
}