package org.opendaylight.restconf.nb.rfc8040.utils.parser;
import static java.util.Objects.requireNonNull;
-import static org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants.SLASH;
-import static org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants.Deserializer.COLON;
-import static org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants.Deserializer.COMMA;
-import static org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants.Deserializer.EMPTY_STRING;
-import static org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants.Deserializer.EQUAL;
-import static org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants.Deserializer.FIRST_ENCODED_CHAR;
-import static org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants.Deserializer.IDENTIFIER;
-import static org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants.Deserializer.IDENTIFIER_FIRST_CHAR;
-import static org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE;
-import static org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants.Deserializer.LAST_ENCODED_CHAR;
-import static org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants.Deserializer.PERCENT_ENCODED_RADIX;
-import static org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants.Deserializer.PERCENT_ENCODING;
import com.google.common.base.CharMatcher;
import com.google.common.collect.ImmutableList;
import java.util.Optional;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
-import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
import org.opendaylight.restconf.common.util.RestUtil;
import org.opendaylight.restconf.common.util.RestconfSchemaUtil;
import org.opendaylight.restconf.nb.rfc8040.codecs.RestCodec;
+import org.opendaylight.yangtools.yang.common.ErrorType;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer;
-import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ContainerLike;
import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
-import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
/**
* Deserializer for {@link String} to {@link YangInstanceIdentifier} for restconf.
*/
public final class YangInstanceIdentifierDeserializer {
+ private static final CharMatcher IDENTIFIER_PREDICATE =
+ CharMatcher.noneOf(ParserConstants.RFC3986_RESERVED_CHARACTERS).precomputed();
private static final String PARSING_FAILED_MESSAGE = "Could not parse Instance Identifier '%s'. "
+ "Offset: '%d' : Reason: ";
-
- private final SchemaContext schemaContext;
+ private static final CharMatcher PERCENT_ENCODING = CharMatcher.is('%');
+ // position of the first encoded char after percent sign in percent encoded string
+ private static final int FIRST_ENCODED_CHAR = 1;
+ // position of the last encoded char after percent sign in percent encoded string
+ private static final int LAST_ENCODED_CHAR = 3;
+ // percent encoded radix for parsing integers
+ private static final int PERCENT_ENCODED_RADIX = 16;
+
+ private final EffectiveModelContext schemaContext;
private final String data;
private DataSchemaContextNode<?> current;
private int offset;
- private YangInstanceIdentifierDeserializer(final SchemaContext schemaContext, final String data) {
+ private YangInstanceIdentifierDeserializer(final EffectiveModelContext schemaContext, final String data) {
this.schemaContext = requireNonNull(schemaContext);
this.data = requireNonNull(data);
current = DataSchemaContextTree.from(schemaContext).getRoot();
* @param data path to data, in URL string form
* @return {@link Iterable} of {@link PathArgument}
*/
- public static Iterable<PathArgument> create(final SchemaContext schemaContext, final String data) {
+ public static Iterable<PathArgument> create(final EffectiveModelContext schemaContext, final String data) {
return new YangInstanceIdentifierDeserializer(schemaContext, data).parse();
}
final QName qname = prepareQName();
// this is the last identifier (input is consumed) or end of identifier (slash)
- if (allCharsConsumed() || currentChar() == SLASH) {
+ if (allCharsConsumed() || currentChar() == '/') {
prepareIdentifier(qname, path);
path.add(current == null ? NodeIdentifier.create(qname) : current.getIdentifier());
- } else if (currentChar() == EQUAL) {
+ } else if (currentChar() == '=') {
if (nextContextNode(qname, path).getDataSchemaNode() instanceof ListSchemaNode) {
prepareNodeWithPredicates(qname, path, (ListSchemaNode) current.getDataSchemaNode());
} else {
skipCurrentChar();
// read key value separated by comma
- while (keys.hasNext() && !allCharsConsumed() && currentChar() != SLASH) {
+ while (keys.hasNext() && !allCharsConsumed() && currentChar() != '/') {
// empty key value
- if (currentChar() == COMMA) {
- values.put(keys.next(), EMPTY_STRING);
+ if (currentChar() == ',') {
+ values.put(keys.next(), "");
skipCurrentChar();
continue;
}
// parse value
final QName key = keys.next();
Optional<DataSchemaNode> leafSchemaNode = listSchemaNode.findDataChildByName(key);
- RestconfDocumentedException.throwIf(!leafSchemaNode.isPresent(), ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT,
+ RestconfDocumentedException.throwIf(leafSchemaNode.isEmpty(), ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT,
"Schema not found for %s", key);
final String value = findAndParsePercentEncoded(nextIdentifierFromNextSequence(IDENTIFIER_PREDICATE));
values.put(key, valueByType);
// skip comma
- if (keys.hasNext() && !allCharsConsumed() && currentChar() == COMMA) {
+ if (keys.hasNext() && !allCharsConsumed() && currentChar() == ',') {
skipCurrentChar();
}
}
// the last key is considered to be empty
if (keys.hasNext()) {
// at this point, it must be true that current char is '/' or all chars have already been consumed
- values.put(keys.next(), EMPTY_STRING);
+ values.put(keys.next(), "");
// there should be no more missing keys
RestconfDocumentedException.throwIf(keys.hasNext(), ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE,
}
final TypeDefinition<?> baseType = RestUtil.resolveBaseTypeFrom(typedef);
if (baseType instanceof LeafrefTypeDefinition) {
- typedef = SchemaContextUtil.getBaseTypeForLeafRef((LeafrefTypeDefinition) baseType, schemaContext,
- schemaNode);
+ typedef = SchemaInferenceStack.ofInstantiatedPath(schemaContext, schemaNode.getPath())
+ .resolveLeafref((LeafrefTypeDefinition) baseType);
}
decoded = RestCodec.from(typedef, null, schemaContext).deserialize(value);
- if (decoded == null) {
- if (baseType instanceof IdentityrefTypeDefinition) {
- decoded = toQName(value, schemaNode, schemaContext);
- }
+ if (decoded == null && typedef instanceof IdentityrefTypeDefinition) {
+ decoded = toIdentityrefQName(value, schemaNode);
}
return decoded;
}
private QName prepareQName() {
checkValidIdentifierStart();
- final String preparedPrefix = nextIdentifierFromNextSequence(IDENTIFIER);
+ final String preparedPrefix = nextIdentifierFromNextSequence(ParserConstants.YANG_IDENTIFIER_PART);
final String prefix;
final String localName;
}
switch (currentChar()) {
- case SLASH:
- case EQUAL:
+ case '/':
+ case '=':
prefix = preparedPrefix;
return getQNameOfDataSchemaNode(prefix);
- case COLON:
+ case ':':
prefix = preparedPrefix;
skipCurrentChar();
checkValidIdentifierStart();
- localName = nextIdentifierFromNextSequence(IDENTIFIER);
+ localName = nextIdentifierFromNextSequence(ParserConstants.YANG_IDENTIFIER_PART);
- if (!allCharsConsumed() && currentChar() == EQUAL) {
+ if (!allCharsConsumed() && currentChar() == '=') {
return getQNameOfDataSchemaNode(localName);
} else {
final Module module = moduleForPrefix(prefix);
}
private void checkValidIdentifierStart() {
- checkValid(IDENTIFIER_FIRST_CHAR.matches(currentChar()), ErrorTag.MALFORMED_MESSAGE,
+ checkValid(ParserConstants.YANG_IDENTIFIER_START.matches(currentChar()), ErrorTag.MALFORMED_MESSAGE,
"Identifier must start with character from set 'a-zA-Z_'");
}
private void validArg() {
// every identifier except of the first MUST start with slash
if (offset != 0) {
- checkValid(SLASH == currentChar(), ErrorTag.MALFORMED_MESSAGE, "Identifier must start with '/'.");
+ checkValid('/' == currentChar(), ErrorTag.MALFORMED_MESSAGE, "Identifier must start with '/'.");
// skip consecutive slashes, users often assume restconf URLs behave just as HTTP does by squashing
// multiple slashes into a single one
- while (!allCharsConsumed() && SLASH == currentChar()) {
+ while (!allCharsConsumed() && '/' == currentChar()) {
skipCurrentChar();
}
private QName getQNameOfDataSchemaNode(final String nodeName) {
final DataSchemaNode dataSchemaNode = current.getDataSchemaNode();
- if (dataSchemaNode instanceof ContainerSchemaNode) {
- return getQNameOfDataSchemaNode((ContainerSchemaNode) dataSchemaNode, nodeName);
+ if (dataSchemaNode instanceof ContainerLike) {
+ return getQNameOfDataSchemaNode((ContainerLike) dataSchemaNode, nodeName);
} else if (dataSchemaNode instanceof ListSchemaNode) {
return getQNameOfDataSchemaNode((ListSchemaNode) dataSchemaNode, nodeName);
}
}
private static String findAndParsePercentEncoded(final String preparedPrefix) {
- if (preparedPrefix.indexOf(PERCENT_ENCODING) == -1) {
+ if (preparedPrefix.indexOf('%') == -1) {
return preparedPrefix;
}
+ // FIXME: this is extremely inefficient: we should be converting ranges of characters, not driven by
+ // CharMatcher, but by String.indexOf()
final StringBuilder parsedPrefix = new StringBuilder(preparedPrefix);
- final CharMatcher matcher = CharMatcher.is(PERCENT_ENCODING);
-
- while (matcher.matchesAnyOf(parsedPrefix)) {
- final int percentCharPosition = matcher.indexIn(parsedPrefix);
+ while (PERCENT_ENCODING.matchesAnyOf(parsedPrefix)) {
+ final int percentCharPosition = PERCENT_ENCODING.indexIn(parsedPrefix);
parsedPrefix.replace(percentCharPosition, percentCharPosition + LAST_ENCODED_CHAR,
String.valueOf((char) Integer.parseInt(parsedPrefix.substring(
percentCharPosition + FIRST_ENCODED_CHAR, percentCharPosition + LAST_ENCODED_CHAR),
return parsedPrefix.toString();
}
- private static Object toQName(final String value, final DataSchemaNode schemaNode,
- final SchemaContext schemaContext) {
+ private QName toIdentityrefQName(final String value, final DataSchemaNode schemaNode) {
final String moduleName = toModuleName(value);
final String nodeName = toNodeName(value);
- final Module module = schemaContext.findModules(moduleName).iterator().next();
- for (final IdentitySchemaNode identitySchemaNode : module.getIdentities()) {
+ final Iterator<? extends Module> modulesIterator = schemaContext.findModules(moduleName).iterator();
+ if (!modulesIterator.hasNext()) {
+ throw new RestconfDocumentedException(String.format("Cannot decode value '%s' for identityref type "
+ + "in %s. Make sure reserved characters such as comma, single-quote, double-quote, colon,"
+ + " double-quote, space, and forward slash (,'\":\" /) are percent-encoded,"
+ + " for example ':' is '%%3A'", value, current.getIdentifier().getNodeType()),
+ ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
+ }
+ for (final IdentitySchemaNode identitySchemaNode : modulesIterator.next().getIdentities()) {
final QName qName = identitySchemaNode.getQName();
if (qName.getLocalName().equals(nodeName)) {
return qName;