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 java.util.Iterator;
11 import java.util.Locale;
12 import java.util.Map.Entry;
13 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
14 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
15 import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
16 import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
17 import org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants;
18 import org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants.Serializer;
19 import org.opendaylight.yangtools.yang.common.QName;
20 import org.opendaylight.yangtools.yang.common.QNameModule;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
25 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
26 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
27 import org.opendaylight.yangtools.yang.model.api.Module;
28 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
31 * Serializer for {@link YangInstanceIdentifier} to {@link String} for restconf.
33 public final class YangInstanceIdentifierSerializer {
35 private YangInstanceIdentifierSerializer() {
36 throw new UnsupportedOperationException("Util class.");
40 * Method to create String from {@link Iterable} of {@link PathArgument}
41 * which are parsing from data by {@link SchemaContext}.
43 * @param schemaContext
44 * for validate of parsing path arguments
47 * @return {@link String}
49 public static String create(final SchemaContext schemaContext, final YangInstanceIdentifier data) {
50 final DataSchemaContextNode<?> current = DataSchemaContextTree.from(schemaContext).getRoot();
51 final MainVarsWrapper variables = new MainVarsWrapper(current);
52 final StringBuilder path = new StringBuilder();
54 QNameModule parentModule = null;
55 for (int i = 0; i < data.getPathArguments().size(); i++) {
56 // get module of the parent
57 if (!variables.getCurrent().isMixin()) {
58 parentModule = variables.getCurrent().getDataSchemaNode().getQName().getModule();
61 final PathArgument arg = data.getPathArguments().get(i);
62 variables.setCurrent(variables.getCurrent().getChild(arg));
63 RestconfDocumentedException.throwIf(variables.getCurrent() == null, ErrorType.APPLICATION,
64 ErrorTag.UNKNOWN_ELEMENT, "Invalid input '%s': schema for argument '%s' (after '%s') not found",
66 if (variables.getCurrent().isMixin()) {
70 // append namespace before every node which is defined in other module than its parent
71 // condition is satisfied also for the first path argument
72 if (!arg.getNodeType().getModule().equals(parentModule)) {
73 // append slash if it is not the first path argument
74 if (path.length() > 0) {
75 path.append(RestconfConstants.SLASH);
78 path.append(prefixForNamespace(arg.getNodeType(), schemaContext));
79 path.append(ParserBuilderConstants.Deserializer.COLON);
81 path.append(RestconfConstants.SLASH);
84 if (arg instanceof NodeIdentifierWithPredicates) {
85 prepareNodeWithPredicates(path, arg);
86 } else if (arg instanceof NodeWithValue) {
87 prepareNodeWithValue(path, arg);
89 appendQName(path, arg.getNodeType());
93 return path.toString();
96 private static void prepareNodeWithValue(final StringBuilder path, final PathArgument arg) {
97 path.append(arg.getNodeType().getLocalName());
98 path.append(ParserBuilderConstants.Deserializer.EQUAL);
100 String value = String.valueOf(((NodeWithValue<String>) arg).getValue());
101 if (Serializer.PERCENT_ENCODE_CHARS.matchesAnyOf(value)) {
102 value = parsePercentEncodeChars(value);
107 private static void prepareNodeWithPredicates(final StringBuilder path, final PathArgument arg) {
108 path.append(arg.getNodeType().getLocalName());
110 final Iterator<Entry<QName, Object>> iterator = ((NodeIdentifierWithPredicates) arg).entrySet().iterator();
111 if (iterator.hasNext()) {
112 path.append(ParserBuilderConstants.Deserializer.EQUAL);
115 while (iterator.hasNext()) {
116 String valueOf = String.valueOf(iterator.next().getValue());
117 if (Serializer.PERCENT_ENCODE_CHARS.matchesAnyOf(valueOf)) {
118 valueOf = parsePercentEncodeChars(valueOf);
120 path.append(valueOf);
121 if (iterator.hasNext()) {
122 path.append(ParserBuilderConstants.Deserializer.COMMA);
128 * Encode {@link Serializer#DISABLED_CHARS} chars to percent encoded chars.
132 * @return encoded {@link String}
134 private static String parsePercentEncodeChars(final String valueOf) {
135 final StringBuilder sb = new StringBuilder();
137 while (start < valueOf.length()) {
138 if (Serializer.PERCENT_ENCODE_CHARS.matches(valueOf.charAt(start))) {
139 final String format = String.format("%x", (int) valueOf.charAt(start));
140 final String upperCase = format.toUpperCase(Locale.ROOT);
141 sb.append(ParserBuilderConstants.Deserializer.PERCENT_ENCODING + upperCase);
143 sb.append(valueOf.charAt(start));
147 return sb.toString();
151 * Add {@link QName} to the serialized string.
154 * {@link StringBuilder}
157 * @return {@link StringBuilder}
159 private static StringBuilder appendQName(final StringBuilder path, final QName qname) {
160 path.append(qname.getLocalName());
165 * Create prefix of namespace from {@link QName}.
169 * @return {@link String}
171 private static String prefixForNamespace(final QName qname, final SchemaContext schemaContext) {
172 final Module module = schemaContext.findModule(qname.getModule()).orElse(null);
173 return module.getName();
176 private static final class MainVarsWrapper {
178 private DataSchemaContextNode<?> current;
180 MainVarsWrapper(final DataSchemaContextNode<?> current) {
181 this.setCurrent(current);
184 public DataSchemaContextNode<?> getCurrent() {
188 public void setCurrent(final DataSchemaContextNode<?> current) {
189 this.current = current;