*/
package org.opendaylight.restconf.nb.rfc8040.utils.parser;
-import static com.google.common.base.Preconditions.checkArgument;
+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.base.CharMatcher;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import java.util.Iterator;
-import java.util.LinkedList;
+import java.text.ParseException;
+import java.util.ArrayList;
import java.util.List;
-import java.util.Optional;
+import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
-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.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.ActionDefinition;
import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer;
-import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
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;
+ }
+
+ Result(final List<PathArgument> steps, final SchemaInferenceStack stack, final SchemaNode node) {
+ path = YangInstanceIdentifier.create(steps);
+ this.stack = requireNonNull(stack);
+ this.node = requireNonNull(node);
+ }
+ }
- private YangInstanceIdentifierDeserializer() {
- throw new UnsupportedOperationException("Util class.");
+ 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<PathArgument> create(final SchemaContext schemaContext, final String data) {
- final List<PathArgument> path = new LinkedList<>();
- final MainVarsWrapper variables = new YangInstanceIdentifierDeserializer.MainVarsWrapper(
- data, DataSchemaContextTree.from(schemaContext).getRoot(),
- YangInstanceIdentifierDeserializer.MainVarsWrapper.STARTING_OFFSET, schemaContext);
-
- while (!variables.allCharsConsumed()) {
- validArg(variables);
- final QName qname = prepareQName(variables);
-
- // this is the last identifier (input is consumed) or end of identifier (slash)
- if (variables.allCharsConsumed() || variables.currentChar() == RestconfConstants.SLASH) {
- prepareIdentifier(qname, path, variables);
- if (variables.getCurrent() == null) {
- path.add(NodeIdentifier.create(qname));
- } else {
- path.add(variables.getCurrent().getIdentifier());
- }
- } else if (variables.currentChar() == ParserBuilderConstants.Deserializer.EQUAL) {
- if (nextContextNode(qname, path, variables).getDataSchemaNode() instanceof ListSchemaNode) {
- prepareNodeWithPredicates(qname, path, variables,
- (ListSchemaNode) variables.getCurrent().getDataSchemaNode());
- } else {
- prepareNodeWithValue(qname, path, variables);
- }
- } else {
- throw new IllegalArgumentException(
- "Bad char " + variables.currentChar() + " on position " + variables.getOffset() + ".");
- }
- }
-
- return ImmutableList.copyOf(path);
+ 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 create(schemaContext, path);
}
- private static void prepareNodeWithPredicates(final QName qname, final List<PathArgument> path,
- final MainVarsWrapper variables, final ListSchemaNode listSchemaNode) {
- variables.checkValid(listSchemaNode != null, "Data schema node is null");
+ public static Result create(final EffectiveModelContext schemaContext, final ApiPath path) {
+ return new YangInstanceIdentifierDeserializer(schemaContext, path).parse();
+ }
- final Iterator<QName> keys = listSchemaNode.getKeyDefinition().iterator();
- final ImmutableMap.Builder<QName, Object> values = ImmutableMap.builder();
+ // 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);
+ }
+
+ // 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);
+ }
+ 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);
+ }
- // skip already expected equal sign
- variables.skipCurrentChar();
+ return new Result(schemaContext, optRpc.orElseThrow().argument());
+ }
- // read key value separated by comma
- while (keys.hasNext() && !variables.allCharsConsumed() && variables.currentChar() != RestconfConstants.SLASH) {
+ final var stack = SchemaInferenceStack.of(schemaContext);
+ final var path = new ArrayList<PathArgument>();
+ final SchemaNode node;
- // empty key value
- if (variables.currentChar() == ParserBuilderConstants.Deserializer.COMMA) {
- values.put(keys.next(), ParserBuilderConstants.Deserializer.EMPTY_STRING);
- variables.skipCurrentChar();
- continue;
- }
+ 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);
+ }
- // check if next value is parsable
- RestconfValidationUtils.checkDocumentedError(
- ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE.matches(variables.currentChar()),
- RestconfError.ErrorType.PROTOCOL,
- RestconfError.ErrorTag.MALFORMED_MESSAGE,
- ""
- );
-
- // parse value
- final QName key = keys.next();
- Optional<DataSchemaNode> leafSchemaNode = listSchemaNode.findDataChildByName(key);
- if (!leafSchemaNode.isPresent()) {
- throw new RestconfDocumentedException("Schema not found for " + key,
- RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT);
+ // 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;
+ }
}
- final String value = findAndParsePercentEncoded(nextIdentifierFromNextSequence(
- ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE, variables));
- final Object valueByType = prepareValueByType(leafSchemaNode.get(), value, variables);
- values.put(key, valueByType);
-
+ // 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);
- // skip comma
- if (keys.hasNext() && !variables.allCharsConsumed()
- && variables.currentChar() == ParserBuilderConstants.Deserializer.COMMA) {
- variables.skipCurrentChar();
+ // 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);
}
- }
- // the last key is considered to be empty
- if (keys.hasNext()) {
- if (variables.allCharsConsumed() || variables.currentChar() == RestconfConstants.SLASH) {
- values.put(keys.next(), ParserBuilderConstants.Deserializer.EMPTY_STRING);
+ 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();
}
- // 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()));
- }
+ path.add(pathArg);
- private static Object prepareValueByType(final DataSchemaNode schemaNode, final String value,
- final MainVarsWrapper vars) {
- Object decoded = null;
-
- TypeDefinition<? extends 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<Object, Object> codec = RestCodec.from(typedef, null, vars.getSchemaContext());
- decoded = codec.deserialize(value);
- if (decoded == null) {
- if (baseType instanceof IdentityrefTypeDefinition) {
- decoded = toQName(value, schemaNode, vars.getSchemaContext());
+ if (!it.hasNext()) {
+ node = childNode.getDataSchemaNode();
+ break;
}
- }
- 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;
+ parentNode = childNode;
+ step = it.next();
+ final var module = step.module();
+ if (module != null) {
+ namespace = resolveNamespace(module);
}
- }
- 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;
+ qname = step.identifier().bindTo(namespace);
}
- return str.substring(idx + 1);
+ return new Result(path, stack, node);
}
- private static String toModuleName(final String str) {
- final int idx = str.indexOf(':');
- if (idx == -1) {
- return null;
+ 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);
}
- if (str.indexOf(':', idx + 1) != -1) {
- return null;
+ final var values = ImmutableMap.<QName, Object>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();
}
- return str.substring(0, idx);
+ return NodeIdentifierWithPredicates.of(qname, values.build());
}
- private static QName prepareQName(final MainVarsWrapper variables) {
- variables.checkValid(ParserBuilderConstants.Deserializer.IDENTIFIER_FIRST_CHAR.matches(variables.currentChar()),
- "Identifier must start with character from set 'a-zA-Z_'");
- final String preparedPrefix = nextIdentifierFromNextSequence(
- ParserBuilderConstants.Deserializer.IDENTIFIER, variables);
- final String prefix;
- final String localName;
+ private Object prepareValueByType(final SchemaInferenceStack stack, final DataSchemaNode schemaNode,
+ final @NonNull String value) {
- if (variables.allCharsConsumed()) {
- return getQNameOfDataSchemaNode(preparedPrefix, variables);
+ TypeDefinition<? extends TypeDefinition<?>> typedef;
+ if (schemaNode instanceof LeafListSchemaNode) {
+ typedef = ((LeafListSchemaNode) schemaNode).getType();
+ } else {
+ typedef = ((LeafSchemaNode) schemaNode).getType();
}
-
- switch (variables.currentChar()) {
- case RestconfConstants.SLASH:
- case ParserBuilderConstants.Deserializer.EQUAL:
- prefix = preparedPrefix;
- return getQNameOfDataSchemaNode(prefix, variables);
- case ParserBuilderConstants.Deserializer.COLON:
- prefix = preparedPrefix;
- variables.skipCurrentChar();
- variables.checkValid(
- ParserBuilderConstants.Deserializer.IDENTIFIER_FIRST_CHAR.matches(variables.currentChar()),
- "Identifier must start with character from set 'a-zA-Z_'");
- localName = nextIdentifierFromNextSequence(ParserBuilderConstants.Deserializer.IDENTIFIER, variables);
-
- if (!variables.allCharsConsumed()
- && variables.currentChar() == ParserBuilderConstants.Deserializer.EQUAL) {
- return getQNameOfDataSchemaNode(localName, variables);
- } else {
- final Module module = moduleForPrefix(prefix, variables.getSchemaContext());
- checkArgument(module != null, "Failed to lookup prefix %s", prefix);
- return QName.create(module.getQNameModule(), localName);
- }
- default:
- throw new IllegalArgumentException("Failed build path.");
+ if (typedef instanceof LeafrefTypeDefinition) {
+ typedef = stack.resolveLeafref((LeafrefTypeDefinition) typedef);
}
- }
-
- 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 (!variables.allCharsConsumed() && matcher.matches(variables.currentChar())) {
- variables.skipCurrentChar();
+ if (typedef instanceof IdentityrefTypeDefinition) {
+ return toIdentityrefQName(value, schemaNode);
}
- }
-
- private static void prepareNodeWithValue(final QName qname, final List<PathArgument> path,
- final MainVarsWrapper variables) {
- variables.skipCurrentChar();
- 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));
- }
- private static void prepareIdentifier(final QName qname, final List<PathArgument> path,
- final MainVarsWrapper variables) {
- final DataSchemaContextNode<?> currentNode = nextContextNode(qname, path, variables);
- if (currentNode == null) {
- return;
- }
- variables.checkValid(!currentNode.isKeyedEntry(),
- "Entry " + qname + " requires key or value predicate to be present");
- }
-
- @SuppressFBWarnings("NP_NULL_ON_SOME_PATH") // code does check for null 'current' but FB doesn't recognize it
- private static DataSchemaContextNode<?> nextContextNode(final QName qname, final List<PathArgument> path,
- final MainVarsWrapper variables) {
- final DataSchemaContextNode<?> initialContext = variables.getCurrent();
- final DataSchemaNode initialDataSchema = initialContext.getDataSchemaNode();
-
- DataSchemaContextNode<?> current = initialContext.getChild(qname);
- variables.setCurrent(current);
-
- if (current == null) {
- final Optional<Module> module = variables.getSchemaContext().findModule(qname.getModule());
- if (module.isPresent()) {
- for (final RpcDefinition rpcDefinition : module.get().getRpcs()) {
- if (rpcDefinition.getQName().getLocalName().equals(qname.getLocalName())) {
- return null;
- }
- }
- }
- if (findActionDefinition(initialDataSchema, qname.getLocalName()).isPresent()) {
- return null;
+ try {
+ if (typedef instanceof InstanceIdentifierTypeDefinition) {
+ return new StringModuleInstanceIdentifierCodec(schemaContext).deserialize(value);
}
- }
- variables.checkValid(current != null, qname + " is not correct schema node identifier.");
- while (current.isMixin()) {
- path.add(current.getIdentifier());
- current = current.getChild(qname);
- variables.setCurrent(current);
- }
- return current;
- }
-
- private static String findAndParsePercentEncoded(final String preparedPrefix) {
- if (!preparedPrefix.contains(String.valueOf(ParserBuilderConstants.Deserializer.PERCENT_ENCODING))) {
- return preparedPrefix;
- }
- 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)));
+ 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);
}
-
- return parsedPrefix.toString();
}
- private static QName getQNameOfDataSchemaNode(final String nodeName, final MainVarsWrapper variables) {
- final DataSchemaNode dataSchemaNode = variables.getCurrent().getDataSchemaNode();
- if (dataSchemaNode instanceof ContainerSchemaNode) {
- return getQNameOfDataSchemaNode((ContainerSchemaNode) dataSchemaNode, nodeName);
- } else if (dataSchemaNode instanceof ListSchemaNode) {
- return getQNameOfDataSchemaNode((ListSchemaNode) dataSchemaNode, nodeName);
- }
-
- throw new UnsupportedOperationException("Unsupported schema node " + dataSchemaNode);
+ private NodeWithValue<?> prepareNodeWithValue(final SchemaInferenceStack stack, final QName qname,
+ final DataSchemaNode schema, final List<String> 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 <T extends DataNodeContainer & SchemaNode & ActionNodeContainer> QName getQNameOfDataSchemaNode(
- final T parent, String nodeName) {
- final Optional<ActionDefinition> actionDef = findActionDefinition(parent, nodeName);
- final SchemaNode node;
- if (actionDef.isPresent()) {
- node = actionDef.get();
+ 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 {
- node = RestconfSchemaUtil.findSchemaNodeInCollection(parent.getChildNodes(), nodeName);
- }
- return node.getQName();
+ namespace = schemaNode.getQName().getModule();
+ localName = value;
+ }
+
+ 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 Module moduleForPrefix(final String prefix, final SchemaContext schemaContext) {
- return schemaContext.findModules(prefix).stream().findFirst().orElse(null);
- }
-
- private static void validArg(final MainVarsWrapper variables) {
- // every identifier except of the first MUST start with slash
- if (variables.getOffset() != MainVarsWrapper.STARTING_OFFSET) {
- variables.checkValid(RestconfConstants.SLASH == variables.currentChar(), "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 (!variables.allCharsConsumed() && RestconfConstants.SLASH == variables.currentChar()) {
- variables.skipCurrentChar();
- }
-
- // check if slash is not also the last char in identifier
- variables.checkValid(!variables.allCharsConsumed(), "Identifier cannot end with '/'.");
- }
- }
-
- private static Optional<ActionDefinition> findActionDefinition(final SchemaNode dataSchemaNode,
- final String nodeName) {
- requireNonNull(dataSchemaNode, "DataSchema Node must not be null.");
- if (dataSchemaNode instanceof ActionNodeContainer) {
- return ((ActionNodeContainer) dataSchemaNode).getActions().stream()
- .filter(actionDef -> actionDef.getQName().getLocalName().equals(nodeName)).findFirst();
- }
- return Optional.empty();
- }
-
- 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;
- }
-
- boolean allCharsConsumed() {
- return offset == data.length();
- }
-
- void checkValid(final boolean condition, final String errorMsg) {
- checkArgument(condition, "Could not parse Instance Identifier '%s'. Offset: %s : Reason: %s", data, offset,
- errorMsg);
- }
-
- char currentChar() {
- return data.charAt(offset);
- }
-
- void skipCurrentChar() {
- offset++;
- }
-
- public String getData() {
- return data;
- }
-
- public DataSchemaContextNode<?> getCurrent() {
- return current;
- }
-
- public void setCurrent(final DataSchemaContextNode<?> current) {
- this.current = current;
- }
-
- public int getOffset() {
- return offset;
- }
-
- public SchemaContext getSchemaContext() {
- return 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();
}
}