2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.restconf.nb.rfc8040.utils.parser;
10 import com.google.common.annotations.VisibleForTesting;
11 import com.google.common.base.CharMatcher;
12 import java.util.Iterator;
13 import java.util.Locale;
14 import java.util.Map.Entry;
15 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
16 import org.opendaylight.yangtools.yang.common.ErrorTag;
17 import org.opendaylight.yangtools.yang.common.ErrorType;
18 import org.opendaylight.yangtools.yang.common.QName;
19 import org.opendaylight.yangtools.yang.common.QNameModule;
20 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
24 import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
25 import org.opendaylight.yangtools.yang.data.util.DataSchemaContext.PathMixin;
26 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
27 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
28 import org.opendaylight.yangtools.yang.model.api.Module;
29 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
32 * Serializer for {@link YangInstanceIdentifier} to {@link String} for restconf.
34 public final class YangInstanceIdentifierSerializer {
35 // RFC8040 specifies that reserved characters need to be percent-encoded
37 static final CharMatcher PERCENT_ENCODE_CHARS =
38 CharMatcher.anyOf(ParserConstants.RFC3986_RESERVED_CHARACTERS).precomputed();
40 private YangInstanceIdentifierSerializer() {
45 * Method to create String from {@link Iterable} of {@link PathArgument}
46 * which are parsing from data by {@link SchemaContext}.
48 * @param schemaContext
49 * for validate of parsing path arguments
52 * @return {@link String}
54 public static String create(final EffectiveModelContext schemaContext, final YangInstanceIdentifier data) {
55 final DataSchemaContext current = DataSchemaContextTree.from(schemaContext).getRoot();
56 final MainVarsWrapper variables = new MainVarsWrapper(current);
57 final StringBuilder path = new StringBuilder();
59 QNameModule parentModule = null;
60 for (final PathArgument arg : data.getPathArguments()) {
61 // get module of the parent
62 final var currentContext = variables.getCurrent();
64 if (!(currentContext instanceof PathMixin)) {
65 parentModule = currentContext.dataSchemaNode().getQName().getModule();
68 final var childContext = currentContext instanceof DataSchemaContext.Composite composite
69 ? composite.childByArg(arg) : null;
71 RestconfDocumentedException.throwIfNull(childContext, ErrorType.APPLICATION,
72 ErrorTag.UNKNOWN_ELEMENT, "Invalid input '%s': schema for argument '%s' (after '%s') not found",
74 variables.setCurrent(childContext);
75 if (childContext instanceof PathMixin) {
79 // append namespace before every node which is defined in other module than its parent
80 // condition is satisfied also for the first path argument
81 if (!arg.getNodeType().getModule().equals(parentModule)) {
82 // append slash if it is not the first path argument
83 if (path.length() > 0) {
87 path.append(prefixForNamespace(arg.getNodeType(), schemaContext)).append(':');
92 if (arg instanceof NodeIdentifierWithPredicates) {
93 prepareNodeWithPredicates(path, arg);
94 } else if (arg instanceof NodeWithValue) {
95 prepareNodeWithValue(path, arg);
97 appendQName(path, arg.getNodeType());
101 return path.toString();
104 private static void prepareNodeWithValue(final StringBuilder path, final PathArgument arg) {
105 path.append(arg.getNodeType().getLocalName());
108 String value = String.valueOf(((NodeWithValue<String>) arg).getValue());
109 if (PERCENT_ENCODE_CHARS.matchesAnyOf(value)) {
110 value = parsePercentEncodeChars(value);
115 private static void prepareNodeWithPredicates(final StringBuilder path, final PathArgument arg) {
116 path.append(arg.getNodeType().getLocalName());
118 final Iterator<Entry<QName, Object>> iterator = ((NodeIdentifierWithPredicates) arg).entrySet().iterator();
119 if (iterator.hasNext()) {
123 while (iterator.hasNext()) {
124 String valueOf = String.valueOf(iterator.next().getValue());
125 if (PERCENT_ENCODE_CHARS.matchesAnyOf(valueOf)) {
126 valueOf = parsePercentEncodeChars(valueOf);
128 path.append(valueOf);
129 if (iterator.hasNext()) {
136 * Encode {@link Serializer#DISABLED_CHARS} chars to percent encoded chars.
140 * @return encoded {@link String}
142 private static String parsePercentEncodeChars(final String valueOf) {
143 final StringBuilder sb = new StringBuilder();
145 for (int i = 0; i < valueOf.length(); ++i) {
146 final char ch = valueOf.charAt(i);
148 if (PERCENT_ENCODE_CHARS.matches(ch)) {
149 final String upperCase = String.format(Locale.ROOT, "%X", (int) ch);
150 sb.append('%').append(upperCase);
155 return sb.toString();
159 * Add {@link QName} to the serialized string.
162 * {@link StringBuilder}
165 * @return {@link StringBuilder}
167 private static StringBuilder appendQName(final StringBuilder path, final QName qname) {
168 path.append(qname.getLocalName());
173 * Create prefix of namespace from {@link QName}.
177 * @return {@link String}
179 private static String prefixForNamespace(final QName qname, final SchemaContext schemaContext) {
180 final Module module = schemaContext.findModule(qname.getModule()).orElse(null);
181 return module.getName();
184 private static final class MainVarsWrapper {
185 private DataSchemaContext current;
187 MainVarsWrapper(final DataSchemaContext current) {
191 public DataSchemaContext getCurrent() {
195 public void setCurrent(final DataSchemaContext current) {
196 this.current = current;