Use DatabindContext in instance identifier serialization
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / nb / rfc8040 / utils / parser / YangInstanceIdentifierSerializer.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.restconf.nb.rfc8040.utils.parser;
9
10 import java.util.Map.Entry;
11 import java.util.Set;
12 import org.opendaylight.restconf.api.ApiPath;
13 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
14 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindContext;
15 import org.opendaylight.yangtools.yang.common.ErrorTag;
16 import org.opendaylight.yangtools.yang.common.ErrorType;
17 import org.opendaylight.yangtools.yang.common.QName;
18 import org.opendaylight.yangtools.yang.common.QNameModule;
19 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
20 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
23 import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
24 import org.opendaylight.yangtools.yang.data.util.DataSchemaContext.PathMixin;
25 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
26
27 /**
28  * Serializer for {@link YangInstanceIdentifier} to {@link String} for restconf.
29  */
30 public final class YangInstanceIdentifierSerializer {
31
32     private YangInstanceIdentifierSerializer() {
33         // Hidden on purpose
34     }
35
36     /**
37      * Method to create String from {@link Iterable} of {@link PathArgument} which are parsing from data with the help
38      * of an {@link EffectiveModelContext}.
39      *
40      * @param databind for validate of parsing path arguments
41      * @param data path to data
42      * @return {@link String}
43      */
44     public static String create(final DatabindContext databind, final YangInstanceIdentifier data) {
45         final var current = databind.schemaTree().getRoot();
46         final var variables = new MainVarsWrapper(current);
47         final var path = new StringBuilder();
48
49         QNameModule parentModule = null;
50         for (final PathArgument arg : data.getPathArguments()) {
51             // get module of the parent
52             final var currentContext = variables.getCurrent();
53
54             if (!(currentContext instanceof PathMixin)) {
55                 parentModule = currentContext.dataSchemaNode().getQName().getModule();
56             }
57
58             final var childContext = currentContext instanceof DataSchemaContext.Composite composite
59                 ? composite.childByArg(arg) : null;
60             if (childContext == null) {
61                 throw new RestconfDocumentedException(
62                     "Invalid input '%s': schema for argument '%s' (after '%s') not found".formatted(data, arg, path),
63                     ErrorType.APPLICATION, ErrorTag.UNKNOWN_ELEMENT);
64             }
65
66             variables.setCurrent(childContext);
67             if (childContext instanceof PathMixin) {
68                 continue;
69             }
70
71             // append namespace before every node which is defined in other module than its parent
72             // condition is satisfied also for the first path argument
73             if (!arg.getNodeType().getModule().equals(parentModule)) {
74                 // append slash if it is not the first path argument
75                 if (path.length() > 0) {
76                     path.append('/');
77                 }
78
79                 path.append(prefixForNamespace(arg.getNodeType(), databind.modelContext())).append(':');
80             } else {
81                 path.append('/');
82             }
83
84             path.append(arg.getNodeType().getLocalName());
85             if (arg instanceof NodeIdentifierWithPredicates withPredicates) {
86                 prepareNodeWithPredicates(path, withPredicates.entrySet());
87             } else if (arg instanceof NodeWithValue<?> withValue) {
88                 prepareNodeWithValue(path, withValue.getValue());
89             }
90         }
91
92         return path.toString();
93     }
94
95     private static void prepareNodeWithValue(final StringBuilder path, final Object value) {
96         path.append('=');
97
98         // FIXME: this is quite fishy
99         final var str = String.valueOf(value);
100         path.append(ApiPath.PERCENT_ESCAPER.escape(str));
101     }
102
103     private static void prepareNodeWithPredicates(final StringBuilder path, final Set<Entry<QName, Object>> entries) {
104         final var iterator = entries.iterator();
105         if (iterator.hasNext()) {
106             path.append('=');
107         }
108
109         while (iterator.hasNext()) {
110             // FIXME: this is quite fishy
111             final var str = String.valueOf(iterator.next().getValue());
112             path.append(ApiPath.PERCENT_ESCAPER.escape(str));
113             if (iterator.hasNext()) {
114                 path.append(',');
115             }
116         }
117     }
118
119     /**
120      * Create prefix of namespace from {@link QName}.
121      *
122      * @param qname {@link QName}
123      * @return {@link String}
124      */
125     private static String prefixForNamespace(final QName qname, final EffectiveModelContext schemaContext) {
126         return schemaContext.findModuleStatement(qname.getModule()).orElseThrow().argument().getLocalName();
127     }
128
129     private static final class MainVarsWrapper {
130         private DataSchemaContext current;
131
132         MainVarsWrapper(final DataSchemaContext current) {
133             setCurrent(current);
134         }
135
136         public DataSchemaContext getCurrent() {
137             return current;
138         }
139
140         public void setCurrent(final DataSchemaContext current) {
141             this.current = current;
142         }
143     }
144 }