X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=restconf%2Frestconf-nb-rfc8040%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Frestconf%2Fnb%2Frfc8040%2Futils%2Fparser%2FYangInstanceIdentifierDeserializer.java;h=9f73482f41aa55cc19fa14d575e6b422a1e61283;hb=bbbd3c4e766d31d63446f43e40545cd0758dab08;hp=7136efbe1785ba151276891c6fd1b86face3e16a;hpb=45cfee1861924b4a8086d38079ce8cbd320386d6;p=netconf.git diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/utils/parser/YangInstanceIdentifierDeserializer.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/utils/parser/YangInstanceIdentifierDeserializer.java index 7136efbe17..9f73482f41 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/utils/parser/YangInstanceIdentifierDeserializer.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/utils/parser/YangInstanceIdentifierDeserializer.java @@ -7,456 +7,314 @@ */ package org.opendaylight.restconf.nb.rfc8040.utils.parser; -import com.google.common.base.CharMatcher; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; +import static com.google.common.base.Verify.verify; +import static com.google.common.base.Verify.verifyNotNull; +import static java.util.Objects.requireNonNull; + import com.google.common.collect.ImmutableMap; -import java.util.Iterator; -import java.util.LinkedList; +import java.text.ParseException; +import java.util.ArrayList; import java.util.List; -import org.opendaylight.restconf.common.errors.RestconfError; -import org.opendaylight.restconf.common.util.RestUtil; -import org.opendaylight.restconf.common.util.RestconfSchemaUtil; -import org.opendaylight.restconf.common.validation.RestconfValidationUtils; -import org.opendaylight.restconf.nb.rfc8040.codecs.RestCodec; -import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants; -import org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants; -import org.opendaylight.yangtools.concepts.Codec; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.restconf.common.errors.RestconfDocumentedException; +import org.opendaylight.restconf.nb.rfc8040.ApiPath; +import org.opendaylight.restconf.nb.rfc8040.ApiPath.ListInstance; +import org.opendaylight.yangtools.yang.common.ErrorTag; +import org.opendaylight.yangtools.yang.common.ErrorType; import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.QNameModule; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; -import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode; +import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec; import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree; -import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; -import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; -import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.RpcDefinition; import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaNode; import org.opendaylight.yangtools.yang.model.api.TypeDefinition; +import org.opendaylight.yangtools.yang.model.api.stmt.IdentityEffectiveStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement; import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition; 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. - * + * Deserializer for {@link String} to {@link YangInstanceIdentifier} for restconf. */ public final class YangInstanceIdentifierDeserializer { + public static final class Result { + public final @NonNull YangInstanceIdentifier path; + public final @NonNull SchemaInferenceStack stack; + public final @NonNull SchemaNode node; + + Result(final EffectiveModelContext context) { + path = YangInstanceIdentifier.empty(); + node = requireNonNull(context); + stack = SchemaInferenceStack.of(context); + } + + Result(final EffectiveModelContext context, final QName qname) { + // Legacy behavior: RPCs do not really have a YangInstanceIdentifier, but the rest of the code expects it + path = YangInstanceIdentifier.of(qname); + stack = SchemaInferenceStack.of(context); + + final var stmt = stack.enterSchemaTree(qname); + verify(stmt instanceof RpcDefinition, "Unexpected statement %s", stmt); + node = (RpcDefinition) stmt; + } - private YangInstanceIdentifierDeserializer() { - throw new UnsupportedOperationException("Util class."); + Result(final List steps, final SchemaInferenceStack stack, final SchemaNode node) { + path = YangInstanceIdentifier.create(steps); + this.stack = requireNonNull(stack); + this.node = requireNonNull(node); + } + } + + private final @NonNull EffectiveModelContext schemaContext; + private final @NonNull ApiPath apiPath; + + private YangInstanceIdentifierDeserializer(final EffectiveModelContext schemaContext, final ApiPath apiPath) { + this.schemaContext = requireNonNull(schemaContext); + this.apiPath = requireNonNull(apiPath); } /** - * Method to create {@link Iterable} from {@link PathArgument} which are - * parsing from data by {@link SchemaContext}. + * Method to create {@link List} from {@link PathArgument} which are parsing from data by {@link SchemaContext}. * - * @param schemaContext - * for validate of parsing path arguments - * @param data - * path to data + * @param schemaContext for validate of parsing path arguments + * @param data path to data, in URL string form * @return {@link Iterable} of {@link PathArgument} + * @throws RestconfDocumentedException the path is not valid */ - public static Iterable create(final SchemaContext schemaContext, final String data) { - final List path = new LinkedList<>(); - final MainVarsWrapper variables = new YangInstanceIdentifierDeserializer.MainVarsWrapper( - data, DataSchemaContextTree.from(schemaContext).getRoot(), - YangInstanceIdentifierDeserializer.MainVarsWrapper.STARTING_OFFSET, schemaContext); - - while (!allCharsConsumed(variables)) { - validArg(variables); - final QName qname = prepareQName(variables); - - // this is the last identifier (input is consumed) or end of identifier (slash) - if (allCharsConsumed(variables) - || currentChar(variables.getOffset(), variables.getData()) == RestconfConstants.SLASH) { - prepareIdentifier(qname, path, variables); - if (variables.getCurrent() == null) { - path.add(NodeIdentifier.create(qname)); - } else { - path.add(variables.getCurrent().getIdentifier()); - } - } else if (currentChar(variables.getOffset(), - variables.getData()) == ParserBuilderConstants.Deserializer.EQUAL) { - if (nextContextNode(qname, path, variables).getDataSchemaNode() instanceof ListSchemaNode) { - prepareNodeWithPredicates(qname, path, variables); - } else { - prepareNodeWithValue(qname, path, variables); - } - } else { - throw new IllegalArgumentException( - "Bad char " + currentChar(variables.getOffset(), variables.getData()) + " on position " - + variables.getOffset() + "."); - } + public static Result create(final EffectiveModelContext schemaContext, final String data) { + final ApiPath path; + try { + path = ApiPath.parse(requireNonNull(data)); + } catch (ParseException e) { + throw new RestconfDocumentedException("Invalid path '" + data + "' at offset " + e.getErrorOffset(), + ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, e); } - - return ImmutableList.copyOf(path); + return create(schemaContext, path); } - private static void prepareNodeWithPredicates(final QName qname, final List path, - final MainVarsWrapper variables) { - - final DataSchemaNode dataSchemaNode = variables.getCurrent().getDataSchemaNode(); - checkValid(dataSchemaNode != null, "Data schema node is null", variables.getData(), variables.getOffset()); - - final Iterator keys = ((ListSchemaNode) dataSchemaNode).getKeyDefinition().iterator(); - final ImmutableMap.Builder values = ImmutableMap.builder(); - - // skip already expected equal sign - skipCurrentChar(variables); + public static Result create(final EffectiveModelContext schemaContext, final ApiPath path) { + return new YangInstanceIdentifierDeserializer(schemaContext, path).parse(); + } - // read key value separated by comma - while (keys.hasNext() && !allCharsConsumed(variables) && currentChar(variables.getOffset(), - variables.getData()) != RestconfConstants.SLASH) { + // FIXME: NETCONF-818: this method really needs to report an Inference and optionally a YangInstanceIdentifier + // - we need the inference for discerning the correct context + // - RPCs do not have a YangInstanceIdentifier + // - Actions always have a YangInstanceIdentifier, but it points to their parent + // - we need to discern the cases RPC invocation, Action invocation and data tree access quickly + // + // All of this really is an utter mess because we end up calling into this code from various places which, + // for example, should not allow RPCs to be valid targets + private Result parse() { + final var it = apiPath.steps().iterator(); + if (!it.hasNext()) { + return new Result(schemaContext); + } - // empty key value - if (currentChar(variables.getOffset(), variables.getData()) == ParserBuilderConstants.Deserializer.COMMA) { - values.put(keys.next(), ParserBuilderConstants.Deserializer.EMPTY_STRING); - skipCurrentChar(variables); - continue; + // First step is somewhat special: + // - it has to contain a module qualifier + // - it has to consider RPCs, for which we need SchemaContext + // + // We therefore peel that first iteration here and not worry about those details in further iterations + var step = it.next(); + final var firstModule = RestconfDocumentedException.throwIfNull(step.module(), + ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, + "First member must use namespace-qualified form, '%s' does not", step.identifier()); + var namespace = resolveNamespace(firstModule); + var qname = step.identifier().bindTo(namespace); + + // We go through more modern APIs here to get this special out of the way quickly + final var optRpc = schemaContext.findModuleStatement(namespace).orElseThrow() + .findSchemaTreeNode(RpcEffectiveStatement.class, qname); + if (optRpc.isPresent()) { + // We have found an RPC match, + if (it.hasNext()) { + throw new RestconfDocumentedException("First step in the path resolves to RPC '" + qname + "' and " + + "therefore it must be the only step present", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE); } - - // check if next value is parsable - RestconfValidationUtils.checkDocumentedError( - ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE - .matches(currentChar(variables.getOffset(), variables.getData())), - RestconfError.ErrorType.PROTOCOL, - RestconfError.ErrorTag.MALFORMED_MESSAGE, - "" - ); - - // parse value - final QName key = keys.next(); - DataSchemaNode leafSchemaNode = null; - if (dataSchemaNode instanceof ListSchemaNode) { - leafSchemaNode = ((ListSchemaNode) dataSchemaNode).getDataChildByName(key); - } else if (dataSchemaNode instanceof LeafListSchemaNode) { - leafSchemaNode = dataSchemaNode; + if (step instanceof ListInstance) { + throw new RestconfDocumentedException("First step in the path resolves to RPC '" + qname + "' and " + + "therefore it must not contain key values", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE); } - final String value = findAndParsePercentEncoded(nextIdentifierFromNextSequence( - ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE, variables)); - final Object valueByType = prepareValueByType(leafSchemaNode, value, variables); - values.put(key, valueByType); - - // skip comma - if (keys.hasNext() && !allCharsConsumed(variables) && currentChar( - variables.getOffset(), variables.getData()) == ParserBuilderConstants.Deserializer.COMMA) { - skipCurrentChar(variables); - } + return new Result(schemaContext, optRpc.orElseThrow().argument()); } - // the last key is considered to be empty - if (keys.hasNext()) { - if (allCharsConsumed(variables) - || currentChar(variables.getOffset(), variables.getData()) == RestconfConstants.SLASH) { - values.put(keys.next(), ParserBuilderConstants.Deserializer.EMPTY_STRING); + final var stack = SchemaInferenceStack.of(schemaContext); + final var path = new ArrayList(); + final SchemaNode node; + + var parentNode = DataSchemaContextTree.from(schemaContext).getRoot(); + while (true) { + final var parentSchema = parentNode.getDataSchemaNode(); + if (parentSchema instanceof ActionNodeContainer) { + final var optAction = ((ActionNodeContainer) parentSchema).findAction(qname); + if (optAction.isPresent()) { + if (it.hasNext()) { + throw new RestconfDocumentedException("Request path resolves to action '" + qname + "' and " + + "therefore it must not continue past it", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE); + } + if (step instanceof ListInstance) { + throw new RestconfDocumentedException("Request path resolves to action '" + qname + "' and " + + "therefore it must not contain key values", + ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE); + } + + // Legacy behavior: Action's path should not include its path, but the rest of the code expects it + path.add(new NodeIdentifier(qname)); + stack.enterSchemaTree(qname); + node = optAction.orElseThrow(); + break; + } } - // there should be no more missing keys - RestconfValidationUtils.checkDocumentedError( - !keys.hasNext(), - RestconfError.ErrorType.PROTOCOL, - RestconfError.ErrorTag.MISSING_ATTRIBUTE, - "Key value missing for: " + qname - ); - } - - path.add(new YangInstanceIdentifier.NodeIdentifierWithPredicates(qname, values.build())); - } + // Resolve the child step with respect to data schema tree + final var found = RestconfDocumentedException.throwIfNull(parentNode.enterChild(stack, qname), + ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, "Schema for '%s' not found", qname); - private static Object prepareValueByType(final DataSchemaNode schemaNode, final String value, - final MainVarsWrapper vars) { - Object decoded = null; - - TypeDefinition> typedef = null; - if (schemaNode instanceof LeafListSchemaNode) { - typedef = ((LeafListSchemaNode) schemaNode).getType(); - } else { - typedef = ((LeafSchemaNode) schemaNode).getType(); - } - final TypeDefinition baseType = RestUtil.resolveBaseTypeFrom(typedef); - if (baseType instanceof LeafrefTypeDefinition) { - typedef = SchemaContextUtil.getBaseTypeForLeafRef((LeafrefTypeDefinition) baseType, vars.getSchemaContext(), - schemaNode); - } - final Codec codec = RestCodec.from(typedef, null, vars.getSchemaContext()); - decoded = codec.deserialize(value); - if (decoded == null) { - if (baseType instanceof IdentityrefTypeDefinition) { - decoded = toQName(value, schemaNode, vars.getSchemaContext()); + // Now add all mixins encountered to the path + var childNode = found; + while (childNode.isMixin()) { + path.add(childNode.getIdentifier()); + childNode = verifyNotNull(childNode.enterChild(stack, qname), + "Mixin %s is missing child for %s while resolving %s", childNode, qname, found); } - } - return decoded; - } - private static Object toQName(final String value, final DataSchemaNode schemaNode, - final SchemaContext schemaContext) { - 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 QName qName = identitySchemaNode.getQName(); - if (qName.getLocalName().equals(nodeName)) { - return qName; + final PathArgument pathArg; + if (step instanceof ListInstance) { + final var values = ((ListInstance) step).keyValues(); + final var schema = childNode.getDataSchemaNode(); + pathArg = schema instanceof ListSchemaNode + ? prepareNodeWithPredicates(stack, qname, (ListSchemaNode) schema, values) + : prepareNodeWithValue(stack, qname, schema, values); + } else { + RestconfDocumentedException.throwIf(childNode.isKeyedEntry(), + ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE, + "Entry '%s' requires key or value predicate to be present.", qname); + pathArg = childNode.getIdentifier(); } - } - return QName.create(schemaNode.getQName().getNamespace(), schemaNode.getQName().getRevision(), nodeName); - } - private static String toNodeName(final String str) { - final int idx = str.indexOf(':'); - if (idx == -1) { - return str; - } - - if (str.indexOf(':', idx + 1) != -1) { - return str; - } + path.add(pathArg); - return str.substring(idx + 1); - } + if (!it.hasNext()) { + node = childNode.getDataSchemaNode(); + break; + } - private static String toModuleName(final String str) { - final int idx = str.indexOf(':'); - if (idx == -1) { - return null; - } + parentNode = childNode; + step = it.next(); + final var module = step.module(); + if (module != null) { + namespace = resolveNamespace(module); + } - if (str.indexOf(':', idx + 1) != -1) { - return null; + qname = step.identifier().bindTo(namespace); } - return str.substring(0, idx); + return new Result(path, stack, node); } - private static QName prepareQName(final MainVarsWrapper variables) { - checkValid( - ParserBuilderConstants.Deserializer.IDENTIFIER_FIRST_CHAR - .matches(currentChar(variables.getOffset(), variables.getData())), - "Identifier must start with character from set 'a-zA-Z_'", variables.getData(), variables.getOffset()); - final String preparedPrefix = nextIdentifierFromNextSequence( - ParserBuilderConstants.Deserializer.IDENTIFIER, variables); - final String prefix; - final String localName; - - if (allCharsConsumed(variables)) { - return getQNameOfDataSchemaNode(preparedPrefix, variables); + private NodeIdentifierWithPredicates prepareNodeWithPredicates(final SchemaInferenceStack stack, final QName qname, + final @NonNull ListSchemaNode schema, final List<@NonNull String> keyValues) { + final var keyDef = schema.getKeyDefinition(); + final var keySize = keyDef.size(); + final var varSize = keyValues.size(); + if (keySize != varSize) { + throw new RestconfDocumentedException( + "Schema for " + qname + " requires " + keySize + " key values, " + varSize + " supplied", + ErrorType.PROTOCOL, keySize > varSize ? ErrorTag.MISSING_ATTRIBUTE : ErrorTag.UNKNOWN_ATTRIBUTE); } - switch (currentChar(variables.getOffset(), variables.getData())) { - case RestconfConstants.SLASH: - prefix = preparedPrefix; - return getQNameOfDataSchemaNode(prefix, variables); - case ParserBuilderConstants.Deserializer.COLON: - prefix = preparedPrefix; - skipCurrentChar(variables); - checkValid( - ParserBuilderConstants.Deserializer.IDENTIFIER_FIRST_CHAR - .matches(currentChar(variables.getOffset(), variables.getData())), - "Identifier must start with character from set 'a-zA-Z_'", variables.getData(), - variables.getOffset()); - localName = nextIdentifierFromNextSequence(ParserBuilderConstants.Deserializer.IDENTIFIER, variables); - - if (!allCharsConsumed(variables) && currentChar( - variables.getOffset(), variables.getData()) == ParserBuilderConstants.Deserializer.EQUAL) { - return getQNameOfDataSchemaNode(localName, variables); - } else { - final Module module = moduleForPrefix(prefix, variables.getSchemaContext()); - Preconditions.checkArgument(module != null, "Failed to lookup prefix %s", prefix); - return QName.create(module.getQNameModule(), localName); - } - case ParserBuilderConstants.Deserializer.EQUAL: - prefix = preparedPrefix; - return getQNameOfDataSchemaNode(prefix, variables); - default: - throw new IllegalArgumentException("Failed build path."); - } - } - - private static String nextIdentifierFromNextSequence(final CharMatcher matcher, final MainVarsWrapper variables) { - final int start = variables.getOffset(); - nextSequenceEnd(matcher, variables); - return variables.getData().substring(start, variables.getOffset()); - } - - private static void nextSequenceEnd(final CharMatcher matcher, final MainVarsWrapper variables) { - while (!allCharsConsumed(variables) - && matcher.matches(variables.getData().charAt(variables.getOffset()))) { - variables.setOffset(variables.getOffset() + 1); + final var values = ImmutableMap.builderWithExpectedSize(keySize); + final var tmp = stack.copy(); + for (int i = 0; i < keySize; ++i) { + final QName keyName = keyDef.get(i); + final var child = schema.getDataChildByName(keyName); + tmp.enterSchemaTree(keyName); + values.put(keyName, prepareValueByType(tmp, child, keyValues.get(i))); + tmp.exit(); } - } - private static void prepareNodeWithValue(final QName qname, final List path, - final MainVarsWrapper variables) { - skipCurrentChar(variables); - final String value = nextIdentifierFromNextSequence( - ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE, variables); - - // exception if value attribute is missing - RestconfValidationUtils.checkDocumentedError( - !value.isEmpty(), - RestconfError.ErrorType.PROTOCOL, - RestconfError.ErrorTag.MISSING_ATTRIBUTE, - "Value missing for: " + qname - ); - final DataSchemaNode dataSchemaNode = variables.getCurrent().getDataSchemaNode(); - final Object valueByType = prepareValueByType(dataSchemaNode, findAndParsePercentEncoded(value), variables); - path.add(new YangInstanceIdentifier.NodeWithValue<>(qname, valueByType)); + return NodeIdentifierWithPredicates.of(qname, values.build()); } - private static void prepareIdentifier(final QName qname, final List path, - final MainVarsWrapper variables) { - final DataSchemaContextNode currentNode = nextContextNode(qname, path, variables); - if (currentNode == null) { - return; - } - checkValid(!currentNode.isKeyedEntry(), "Entry " + qname + " requires key or value predicate to be present", - variables.getData(), variables.getOffset()); - } + private Object prepareValueByType(final SchemaInferenceStack stack, final DataSchemaNode schemaNode, + final @NonNull String value) { - private static DataSchemaContextNode nextContextNode(final QName qname, final List path, - final MainVarsWrapper variables) { - variables.setCurrent(variables.getCurrent().getChild(qname)); - DataSchemaContextNode current = variables.getCurrent(); - if (current == null) { - for (final RpcDefinition rpcDefinition : variables.getSchemaContext() - .findModule(qname.getModule()).orElse(null).getRpcs()) { - if (rpcDefinition.getQName().getLocalName().equals(qname.getLocalName())) { - return null; - } - } - } - checkValid(current != null, qname + " is not correct schema node identifier.", variables.getData(), - variables.getOffset()); - while (current.isMixin()) { - path.add(current.getIdentifier()); - current = current.getChild(qname); - variables.setCurrent(current); + TypeDefinition> typedef; + if (schemaNode instanceof LeafListSchemaNode) { + typedef = ((LeafListSchemaNode) schemaNode).getType(); + } else { + typedef = ((LeafSchemaNode) schemaNode).getType(); } - return current; - } - - private static String findAndParsePercentEncoded(final String preparedPrefix) { - if (!preparedPrefix.contains(String.valueOf(ParserBuilderConstants.Deserializer.PERCENT_ENCODING))) { - return preparedPrefix; + if (typedef instanceof LeafrefTypeDefinition) { + typedef = stack.resolveLeafref((LeafrefTypeDefinition) typedef); } - final StringBuilder parsedPrefix = new StringBuilder(preparedPrefix); - final CharMatcher matcher = CharMatcher.is(ParserBuilderConstants.Deserializer.PERCENT_ENCODING); - - while (matcher.matchesAnyOf(parsedPrefix)) { - final int percentCharPosition = matcher.indexIn(parsedPrefix); - parsedPrefix.replace( - percentCharPosition, - percentCharPosition + ParserBuilderConstants.Deserializer.LAST_ENCODED_CHAR, - String.valueOf((char) Integer.parseInt(parsedPrefix.substring( - percentCharPosition + ParserBuilderConstants.Deserializer.FIRST_ENCODED_CHAR, - percentCharPosition + ParserBuilderConstants.Deserializer.LAST_ENCODED_CHAR), - ParserBuilderConstants.Deserializer.PERCENT_ENCODED_RADIX))); + if (typedef instanceof IdentityrefTypeDefinition) { + return toIdentityrefQName(value, schemaNode); } - return parsedPrefix.toString(); - } + try { + if (typedef instanceof InstanceIdentifierTypeDefinition) { + return new StringModuleInstanceIdentifierCodec(schemaContext).deserialize(value); + } - private static QName getQNameOfDataSchemaNode(final String nodeName, final MainVarsWrapper variables) { - final DataSchemaNode dataSchemaNode = variables.getCurrent().getDataSchemaNode(); - if (dataSchemaNode instanceof ContainerSchemaNode) { - final ContainerSchemaNode contSchemaNode = (ContainerSchemaNode) dataSchemaNode; - final DataSchemaNode node = RestconfSchemaUtil.findSchemaNodeInCollection(contSchemaNode.getChildNodes(), - nodeName); - return node.getQName(); - } else if (dataSchemaNode instanceof ListSchemaNode) { - final ListSchemaNode listSchemaNode = (ListSchemaNode) dataSchemaNode; - final DataSchemaNode node = RestconfSchemaUtil.findSchemaNodeInCollection(listSchemaNode.getChildNodes(), - nodeName); - return node.getQName(); + return verifyNotNull(TypeDefinitionAwareCodec.from(typedef), + "Unhandled type %s decoding %s", typedef, value).deserialize(value); + } catch (IllegalArgumentException e) { + throw new RestconfDocumentedException("Invalid value '" + value + "' for " + schemaNode.getQName(), + ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e); } - throw new UnsupportedOperationException(); } - private static Module moduleForPrefix(final String prefix, final SchemaContext schemaContext) { - return schemaContext.findModules(prefix).stream().findFirst().orElse(null); + private NodeWithValue prepareNodeWithValue(final SchemaInferenceStack stack, final QName qname, + final DataSchemaNode schema, final List keyValues) { + // TODO: qname should be always equal to schema.getQName(), right? + return new NodeWithValue<>(qname, prepareValueByType(stack, schema, + // FIXME: ahem: we probably want to do something differently here + keyValues.get(0))); } - private static void validArg(final MainVarsWrapper variables) { - // every identifier except of the first MUST start with slash - if (variables.getOffset() != MainVarsWrapper.STARTING_OFFSET) { - checkValid(RestconfConstants.SLASH == currentChar(variables.getOffset(), variables.getData()), - "Identifier must start with '/'.", variables.getData(), variables.getOffset()); - - // skip slash - skipCurrentChar(variables); - - // check if slash is not also the last char in identifier - checkValid(!allCharsConsumed(variables), "Identifier cannot end with '/'.", - variables.getData(), variables.getOffset()); + private QName toIdentityrefQName(final String value, final DataSchemaNode schemaNode) { + final QNameModule namespace; + final String localName; + final int firstColon = value.indexOf(':'); + if (firstColon != -1) { + namespace = resolveNamespace(value.substring(0, firstColon)); + localName = value.substring(firstColon + 1); + } else { + namespace = schemaNode.getQName().getModule(); + localName = value; } - } - - private static void skipCurrentChar(final MainVarsWrapper variables) { - variables.setOffset(variables.getOffset() + 1); - } - - private static char currentChar(final int offset, final String data) { - return data.charAt(offset); - } - - private static void checkValid(final boolean condition, final String errorMsg, final String data, - final int offset) { - Preconditions.checkArgument(condition, "Could not parse Instance Identifier '%s'. Offset: %s : Reason: %s", - data, offset, errorMsg); - } - private static boolean allCharsConsumed(final MainVarsWrapper variables) { - return variables.getOffset() == variables.getData().length(); + return schemaContext.getModuleStatement(namespace) + .streamEffectiveSubstatements(IdentityEffectiveStatement.class) + .map(IdentityEffectiveStatement::argument) + .filter(qname -> localName.equals(qname.getLocalName())) + .findFirst() + .orElseThrow(() -> new RestconfDocumentedException( + "No identity found for '" + localName + "' in namespace " + namespace, + ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE)); } - private static final class MainVarsWrapper { - private static final int STARTING_OFFSET = 0; - - private final SchemaContext schemaContext; - private final String data; - - private DataSchemaContextNode current; - private int offset; - - MainVarsWrapper(final String data, final DataSchemaContextNode current, final int offset, - final SchemaContext schemaContext) { - this.data = data; - this.current = current; - this.offset = offset; - this.schemaContext = schemaContext; - } - - public String getData() { - return this.data; - } - - public DataSchemaContextNode getCurrent() { - return this.current; - } - - public void setCurrent(final DataSchemaContextNode current) { - this.current = current; - } - - public int getOffset() { - return this.offset; - } - - public void setOffset(final int offset) { - this.offset = offset; - } - - public SchemaContext getSchemaContext() { - return this.schemaContext; - } + private @NonNull QNameModule resolveNamespace(final String moduleName) { + final var modules = schemaContext.findModules(moduleName); + RestconfDocumentedException.throwIf(modules.isEmpty(), ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT, + "Failed to lookup for module with name '%s'.", moduleName); + return modules.iterator().next().getQNameModule(); } }