Remove RestconfError.ErrorType
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / utils / parser / YangInstanceIdentifierDeserializer.java
index c2f7ea8f6b1d3d9c80e15eff461c32656cdcd4e0..275b91a59cad82bac29cf2db7c28571f1deb3b9f 100644 (file)
@@ -8,18 +8,6 @@
 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;
@@ -31,10 +19,10 @@ import java.util.List;
 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;
@@ -43,9 +31,10 @@ import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
 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;
@@ -57,22 +46,31 @@ import org.opendaylight.yangtools.yang.model.api.SchemaNode;
 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();
@@ -85,7 +83,7 @@ public final class YangInstanceIdentifierDeserializer {
      * @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();
     }
 
@@ -97,10 +95,10 @@ public final class YangInstanceIdentifierDeserializer {
             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 {
@@ -125,11 +123,11 @@ public final class YangInstanceIdentifierDeserializer {
         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;
             }
@@ -141,7 +139,7 @@ public final class YangInstanceIdentifierDeserializer {
             // 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));
@@ -149,7 +147,7 @@ public final class YangInstanceIdentifierDeserializer {
             values.put(key, valueByType);
 
             // skip comma
-            if (keys.hasNext() && !allCharsConsumed() && currentChar() == COMMA) {
+            if (keys.hasNext() && !allCharsConsumed() && currentChar() == ',') {
                 skipCurrentChar();
             }
         }
@@ -157,7 +155,7 @@ public final class YangInstanceIdentifierDeserializer {
         // 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,
@@ -178,21 +176,19 @@ public final class YangInstanceIdentifierDeserializer {
         }
         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;
 
@@ -201,17 +197,17 @@ public final class YangInstanceIdentifierDeserializer {
         }
 
         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);
@@ -292,7 +288,7 @@ public final class YangInstanceIdentifierDeserializer {
     }
 
     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_'");
     }
 
@@ -321,11 +317,11 @@ public final class YangInstanceIdentifierDeserializer {
     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();
             }
 
@@ -336,8 +332,8 @@ public final class YangInstanceIdentifierDeserializer {
 
     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);
         }
@@ -368,15 +364,15 @@ public final class YangInstanceIdentifierDeserializer {
     }
 
     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),
@@ -386,12 +382,18 @@ public final class YangInstanceIdentifierDeserializer {
         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;