*/
package org.opendaylight.restconf.nb.rfc8040.utils.parser;
-import static com.google.common.base.Preconditions.checkArgument;
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.ArrayList;
import java.util.Iterator;
-import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
-import org.opendaylight.restconf.common.errors.RestconfError;
+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.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.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.util.SchemaContextUtil;
/**
- * Deserializer for {@link String} to {@link YangInstanceIdentifier} for
- * restconf.
- *
+ * Deserializer for {@link String} to {@link YangInstanceIdentifier} for restconf.
*/
public final class YangInstanceIdentifierDeserializer {
-
- private YangInstanceIdentifierDeserializer() {
- throw new UnsupportedOperationException("Util class.");
+ 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 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 EffectiveModelContext schemaContext, final String data) {
+ this.schemaContext = requireNonNull(schemaContext);
+ this.data = requireNonNull(data);
+ current = DataSchemaContextTree.from(schemaContext).getRoot();
}
/**
- * Method to create {@link Iterable} from {@link PathArgument} which are
- * parsing from data by {@link SchemaContext}.
+ * Method to create {@link Iterable} 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}
*/
- 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);
+ public static Iterable<PathArgument> create(final EffectiveModelContext schemaContext, final String data) {
+ return new YangInstanceIdentifierDeserializer(schemaContext, data).parse();
+ }
+
+ private List<PathArgument> parse() {
+ final List<PathArgument> path = new ArrayList<>();
- while (!variables.allCharsConsumed()) {
- variables.validArg();
- final QName qname = prepareQName(variables);
+ while (!allCharsConsumed()) {
+ validArg();
+ final QName qname = prepareQName();
// 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));
+ if (allCharsConsumed() || currentChar() == '/') {
+ prepareIdentifier(qname, path);
+ path.add(current == null ? NodeIdentifier.create(qname) : current.getIdentifier());
+ } else if (currentChar() == '=') {
+ if (nextContextNode(qname, path).getDataSchemaNode() instanceof ListSchemaNode) {
+ prepareNodeWithPredicates(qname, path, (ListSchemaNode) current.getDataSchemaNode());
} 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);
+ prepareNodeWithValue(qname, path);
}
} else {
- throw new IllegalArgumentException(
- "Bad char " + variables.currentChar() + " on position " + variables.getOffset() + ".");
+ throw getParsingCharFailedException();
}
}
return ImmutableList.copyOf(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");
+ private void prepareNodeWithPredicates(final QName qname, final List<PathArgument> path,
+ final ListSchemaNode listSchemaNode) {
+ checkValid(listSchemaNode != null, ErrorTag.MALFORMED_MESSAGE, "Data schema node is null");
final Iterator<QName> keys = listSchemaNode.getKeyDefinition().iterator();
final ImmutableMap.Builder<QName, Object> values = ImmutableMap.builder();
// skip already expected equal sign
- variables.skipCurrentChar();
+ skipCurrentChar();
// read key value separated by comma
- while (keys.hasNext() && !variables.allCharsConsumed() && variables.currentChar() != RestconfConstants.SLASH) {
+ while (keys.hasNext() && !allCharsConsumed() && currentChar() != '/') {
// empty key value
- if (variables.currentChar() == ParserBuilderConstants.Deserializer.COMMA) {
- values.put(keys.next(), ParserBuilderConstants.Deserializer.EMPTY_STRING);
- variables.skipCurrentChar();
+ if (currentChar() == ',') {
+ values.put(keys.next(), "");
+ skipCurrentChar();
continue;
}
// check if next value is parsable
- RestconfValidationUtils.checkDocumentedError(
- ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE.matches(variables.currentChar()),
- RestconfError.ErrorType.PROTOCOL,
- RestconfError.ErrorTag.MALFORMED_MESSAGE,
- ""
- );
+ checkValid(IDENTIFIER_PREDICATE.matches(currentChar()), ErrorTag.MALFORMED_MESSAGE,
+ "Value that starts with character %c is not parsable.", currentChar());
// 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);
- }
+ RestconfDocumentedException.throwIf(leafSchemaNode.isEmpty(), ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT,
+ "Schema not found for %s", key);
- final String value = findAndParsePercentEncoded(variables.nextIdentifierFromNextSequence(
- ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE));
- final Object valueByType = prepareValueByType(leafSchemaNode.get(), value, variables);
+ final String value = findAndParsePercentEncoded(nextIdentifierFromNextSequence(IDENTIFIER_PREDICATE));
+ final Object valueByType = prepareValueByType(leafSchemaNode.get(), value);
values.put(key, valueByType);
-
// skip comma
- if (keys.hasNext() && !variables.allCharsConsumed()
- && variables.currentChar() == ParserBuilderConstants.Deserializer.COMMA) {
- variables.skipCurrentChar();
+ if (keys.hasNext() && !allCharsConsumed() && currentChar() == ',') {
+ skipCurrentChar();
}
}
// 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);
- }
+ // at this point, it must be true that current char is '/' or all chars have already been consumed
+ values.put(keys.next(), "");
// there should be no more missing keys
- RestconfValidationUtils.checkDocumentedError(
- !keys.hasNext(),
- RestconfError.ErrorType.PROTOCOL,
- RestconfError.ErrorTag.MISSING_ATTRIBUTE,
- "Key value missing for: " + qname
- );
+ RestconfDocumentedException.throwIf(keys.hasNext(), ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE,
+ "Cannot parse input identifier '%s'. Key value is missing for QName: %s", data, qname);
}
- path.add(new YangInstanceIdentifier.NodeIdentifierWithPredicates(qname, values.build()));
+ path.add(YangInstanceIdentifier.NodeIdentifierWithPredicates.of(qname, values.build()));
}
- private static Object prepareValueByType(final DataSchemaNode schemaNode, final String value,
- final MainVarsWrapper vars) {
- Object decoded = null;
+ private Object prepareValueByType(final DataSchemaNode schemaNode, final String value) {
+ Object decoded;
- TypeDefinition<? extends TypeDefinition<?>> typedef = null;
+ TypeDefinition<? extends TypeDefinition<?>> typedef;
if (schemaNode instanceof LeafListSchemaNode) {
typedef = ((LeafListSchemaNode) schemaNode).getType();
} else {
}
final TypeDefinition<?> baseType = RestUtil.resolveBaseTypeFrom(typedef);
if (baseType instanceof LeafrefTypeDefinition) {
- typedef = SchemaContextUtil.getBaseTypeForLeafRef((LeafrefTypeDefinition) baseType, vars.getSchemaContext(),
+ typedef = SchemaContextUtil.getBaseTypeForLeafRef((LeafrefTypeDefinition) baseType, schemaContext,
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());
- }
+ decoded = RestCodec.from(typedef, null, schemaContext).deserialize(value);
+ if (decoded == null && typedef instanceof IdentityrefTypeDefinition) {
+ decoded = toIdentityrefQName(value, schemaNode);
}
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;
- }
- }
- 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;
- }
-
- return str.substring(idx + 1);
- }
-
- private static String toModuleName(final String str) {
- final int idx = str.indexOf(':');
- if (idx == -1) {
- return null;
- }
-
- if (str.indexOf(':', idx + 1) != -1) {
- return null;
- }
-
- return str.substring(0, idx);
- }
-
- 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 = variables.nextIdentifierFromNextSequence(
- ParserBuilderConstants.Deserializer.IDENTIFIER);
+ private QName prepareQName() {
+ checkValidIdentifierStart();
+ final String preparedPrefix = nextIdentifierFromNextSequence(ParserConstants.YANG_IDENTIFIER_PART);
final String prefix;
final String localName;
- if (variables.allCharsConsumed()) {
- return getQNameOfDataSchemaNode(preparedPrefix, variables);
+ if (allCharsConsumed()) {
+ return getQNameOfDataSchemaNode(preparedPrefix);
}
- switch (variables.currentChar()) {
- case RestconfConstants.SLASH:
- case ParserBuilderConstants.Deserializer.EQUAL:
+ switch (currentChar()) {
+ case '/':
+ case '=':
prefix = preparedPrefix;
- return getQNameOfDataSchemaNode(prefix, variables);
- case ParserBuilderConstants.Deserializer.COLON:
+ return getQNameOfDataSchemaNode(prefix);
+ case ':':
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 = variables.nextIdentifierFromNextSequence(ParserBuilderConstants.Deserializer.IDENTIFIER);
-
- if (!variables.allCharsConsumed()
- && variables.currentChar() == ParserBuilderConstants.Deserializer.EQUAL) {
- return getQNameOfDataSchemaNode(localName, variables);
+ skipCurrentChar();
+ checkValidIdentifierStart();
+ localName = nextIdentifierFromNextSequence(ParserConstants.YANG_IDENTIFIER_PART);
+
+ if (!allCharsConsumed() && currentChar() == '=') {
+ return getQNameOfDataSchemaNode(localName);
} else {
- final Module module = moduleForPrefix(prefix, variables.getSchemaContext());
- checkArgument(module != null, "Failed to lookup prefix %s", prefix);
+ final Module module = moduleForPrefix(prefix);
+ RestconfDocumentedException.throwIf(module == null, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT,
+ "Failed to lookup for module with name '%s'.", prefix);
return QName.create(module.getQNameModule(), localName);
}
default:
- throw new IllegalArgumentException("Failed build path.");
+ throw getParsingCharFailedException();
}
}
- private static void prepareNodeWithValue(final QName qname, final List<PathArgument> path,
- final MainVarsWrapper variables) {
- variables.skipCurrentChar();
- final String value = variables.nextIdentifierFromNextSequence(
- ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE);
+ private void prepareNodeWithValue(final QName qname, final List<PathArgument> path) {
+ skipCurrentChar();
+ final String value = nextIdentifierFromNextSequence(IDENTIFIER_PREDICATE);
// 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);
+ RestconfDocumentedException.throwIf(value.isEmpty(), ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE,
+ "Cannot parse input identifier '%s' - value is missing for QName: %s.", data, qname);
+ final DataSchemaNode dataSchemaNode = current.getDataSchemaNode();
+ final Object valueByType = prepareValueByType(dataSchemaNode, findAndParsePercentEncoded(value));
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;
+ private void prepareIdentifier(final QName qname, final List<PathArgument> path) {
+ final DataSchemaContextNode<?> currentNode = nextContextNode(qname, path);
+ if (currentNode != null) {
+ checkValid(!currentNode.isKeyedEntry(), ErrorTag.MISSING_ATTRIBUTE,
+ "Entry '%s' requires key or value predicate to be present.", qname);
}
- 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();
+ @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH",
+ justification = "code does check for null 'current' but FB doesn't recognize it")
+ private DataSchemaContextNode<?> nextContextNode(final QName qname, final List<PathArgument> path) {
+ final DataSchemaContextNode<?> initialContext = current;
final DataSchemaNode initialDataSchema = initialContext.getDataSchemaNode();
- DataSchemaContextNode<?> current = initialContext.getChild(qname);
- variables.setCurrent(current);
+ current = initialContext.getChild(qname);
if (current == null) {
- final Optional<Module> module = variables.getSchemaContext().findModule(qname.getModule());
+ final Optional<Module> module = schemaContext.findModule(qname.getModule());
if (module.isPresent()) {
for (final RpcDefinition rpcDefinition : module.get().getRpcs()) {
if (rpcDefinition.getQName().getLocalName().equals(qname.getLocalName())) {
return null;
}
}
- variables.checkValid(current != null, qname + " is not correct schema node identifier.");
+ checkValid(current != null, ErrorTag.MALFORMED_MESSAGE, "'%s' is not correct schema node identifier.", qname);
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;
- }
+ private Module moduleForPrefix(final String prefix) {
+ return schemaContext.findModules(prefix).stream().findFirst().orElse(null);
+ }
- final StringBuilder parsedPrefix = new StringBuilder(preparedPrefix);
- final CharMatcher matcher = CharMatcher.is(ParserBuilderConstants.Deserializer.PERCENT_ENCODING);
+ private boolean allCharsConsumed() {
+ return offset == data.length();
+ }
- 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)));
+ private void checkValid(final boolean condition, final ErrorTag errorTag, final String errorMsg,
+ final Object... messageArgs) {
+ final Object[] allMessageArguments = new Object[messageArgs.length + 2];
+ allMessageArguments[0] = data;
+ allMessageArguments[1] = offset;
+ System.arraycopy(messageArgs, 0, allMessageArguments, 2, messageArgs.length);
+ RestconfDocumentedException.throwIf(!condition, ErrorType.PROTOCOL, errorTag,
+ PARSING_FAILED_MESSAGE + errorMsg, allMessageArguments);
+ }
+
+ private void checkValidIdentifierStart() {
+ checkValid(ParserConstants.YANG_IDENTIFIER_START.matches(currentChar()), ErrorTag.MALFORMED_MESSAGE,
+ "Identifier must start with character from set 'a-zA-Z_'");
+ }
+
+ private RestconfDocumentedException getParsingCharFailedException() {
+ return new RestconfDocumentedException(String.format(PARSING_FAILED_MESSAGE, data, offset)
+ + String.format("Bad char '%c' on the current position.", currentChar()),
+ ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
+
+ private char currentChar() {
+ return data.charAt(offset);
+ }
+
+ private void skipCurrentChar() {
+ offset++;
+ }
+
+ private String nextIdentifierFromNextSequence(final CharMatcher matcher) {
+ final int start = offset;
+ while (!allCharsConsumed() && matcher.matches(currentChar())) {
+ skipCurrentChar();
}
+ return data.substring(start, offset);
+ }
- return parsedPrefix.toString();
+ private void validArg() {
+ // every identifier except of the first MUST start with slash
+ if (offset != 0) {
+ 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() && '/' == currentChar()) {
+ skipCurrentChar();
+ }
+
+ // check if slash is not also the last char in identifier
+ checkValid(!allCharsConsumed(), ErrorTag.MALFORMED_MESSAGE, "Identifier cannot end with '/'.");
+ }
}
- 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);
+ private QName getQNameOfDataSchemaNode(final String nodeName) {
+ final DataSchemaNode dataSchemaNode = current.getDataSchemaNode();
+ if (dataSchemaNode instanceof ContainerLike) {
+ return getQNameOfDataSchemaNode((ContainerLike) dataSchemaNode, nodeName);
} else if (dataSchemaNode instanceof ListSchemaNode) {
return getQNameOfDataSchemaNode((ListSchemaNode) dataSchemaNode, nodeName);
}
}
private static <T extends DataNodeContainer & SchemaNode & ActionNodeContainer> QName getQNameOfDataSchemaNode(
- final T parent, String nodeName) {
- final Optional<ActionDefinition> actionDef = findActionDefinition(parent, nodeName);
+ final T parent, final String nodeName) {
+ final Optional<? extends ActionDefinition> actionDef = findActionDefinition(parent, nodeName);
final SchemaNode node;
if (actionDef.isPresent()) {
node = actionDef.get();
return node.getQName();
}
- private static Module moduleForPrefix(final String prefix, final SchemaContext schemaContext) {
- return schemaContext.findModules(prefix).stream().findFirst().orElse(null);
- }
-
- private static Optional<ActionDefinition> findActionDefinition(final SchemaNode dataSchemaNode,
+ private static Optional<? extends ActionDefinition> findActionDefinition(final SchemaNode dataSchemaNode,
final String nodeName) {
requireNonNull(dataSchemaNode, "DataSchema Node must not be null.");
if (dataSchemaNode instanceof ActionNodeContainer) {
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);
+ private static String findAndParsePercentEncoded(final String preparedPrefix) {
+ if (preparedPrefix.indexOf('%') == -1) {
+ return preparedPrefix;
}
- char currentChar() {
- return data.charAt(offset);
+ // 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);
+ 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),
+ PERCENT_ENCODED_RADIX)));
}
- void skipCurrentChar() {
- offset++;
- }
+ return parsedPrefix.toString();
+ }
- String nextIdentifierFromNextSequence(final CharMatcher matcher) {
- final int start = offset;
- while (!allCharsConsumed() && matcher.matches(currentChar())) {
- skipCurrentChar();
+ private QName toIdentityrefQName(final String value, final DataSchemaNode schemaNode) {
+ final String moduleName = toModuleName(value);
+ final String nodeName = toNodeName(value);
+ 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;
}
- return data.substring(start, offset);
}
+ return QName.create(schemaNode.getQName().getNamespace(), schemaNode.getQName().getRevision(), nodeName);
+ }
- void validArg() {
- // every identifier except of the first MUST start with slash
- if (offset != MainVarsWrapper.STARTING_OFFSET) {
- checkValid(RestconfConstants.SLASH == 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 (!allCharsConsumed() && RestconfConstants.SLASH == currentChar()) {
- skipCurrentChar();
- }
-
- // check if slash is not also the last char in identifier
- checkValid(!allCharsConsumed(), "Identifier cannot end with '/'.");
- }
+ private static String toNodeName(final String str) {
+ final int idx = str.indexOf(':');
+ if (idx == -1) {
+ return str;
}
- public DataSchemaContextNode<?> getCurrent() {
- return current;
+ if (str.indexOf(':', idx + 1) != -1) {
+ return str;
}
- public void setCurrent(final DataSchemaContextNode<?> current) {
- this.current = current;
- }
+ return str.substring(idx + 1);
+ }
- public int getOffset() {
- return offset;
+ private static String toModuleName(final String str) {
+ final int idx = str.indexOf(':');
+ if (idx == -1) {
+ return null;
}
- public SchemaContext getSchemaContext() {
- return schemaContext;
+ if (str.indexOf(':', idx + 1) != -1) {
+ return null;
}
+
+ return str.substring(0, idx);
}
}