538dbdc3d40b84105b90d7f2a4bf5cef451d3f9a
[netconf.git] / restconf / sal-rest-connector / src / main / java / org / opendaylight / restconf / parser / builder / 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.parser.builder;
9
10
11 import com.google.common.base.Preconditions;
12 import java.net.URI;
13 import java.util.Iterator;
14 import java.util.Map.Entry;
15 import org.opendaylight.restconf.utils.RestconfConstants;
16 import org.opendaylight.restconf.utils.parser.builder.ParserBuilderConstants;
17 import org.opendaylight.restconf.utils.parser.builder.ParserBuilderConstants.Serializer;
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.DataSchemaContextNode;
25 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
26 import org.opendaylight.yangtools.yang.model.api.Module;
27 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
28
29 /**
30  * Serializer for {@link YangInstanceIdentifier} to {@link String} for restconf
31  *
32  */
33 public final class YangInstanceIdentifierSerializer {
34
35     private YangInstanceIdentifierSerializer() {
36         throw new UnsupportedOperationException("Util class.");
37     }
38
39     /**
40      * Method to create String from {@link Iterable} of {@link PathArgument}
41      * which are parsing from data by {@link SchemaContext}
42      *
43      * @param schemaContext
44      *            - for validate of parsing path arguments
45      * @param data
46      *            - path to data
47      * @return {@link String}
48      */
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();
53
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();
59             }
60
61             final PathArgument arg = data.getPathArguments().get(i);
62             variables.setCurrent(variables.getCurrent().getChild(arg));
63
64             Preconditions.checkArgument(variables.getCurrent() != null,
65                     "Invalid input %s: schema for argument %s (after %s) not found", data, arg, path);
66
67             if (variables.getCurrent().isMixin()) {
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(RestconfConstants.SLASH);
77                 }
78
79                 path.append(prefixForNamespace(arg.getNodeType(), schemaContext));
80                 path.append(ParserBuilderConstants.Deserializer.COLON);
81             } else {
82                 path.append(RestconfConstants.SLASH);
83             }
84
85             if (arg instanceof NodeIdentifierWithPredicates) {
86                 prepareNodeWithPredicates(path, arg);
87             } else if (arg instanceof NodeWithValue) {
88                 prepareNodeWithValue(path, arg);
89             } else {
90                 appendQName(path, arg.getNodeType());
91             }
92         }
93
94         return path.toString();
95     }
96
97     private static void prepareNodeWithValue(final StringBuilder path, final PathArgument arg) {
98         path.append(arg.getNodeType().getLocalName());
99         path.append(ParserBuilderConstants.Deserializer.EQUAL);
100
101         String value = ((NodeWithValue<String>) arg).getValue();
102         if (Serializer.PERCENT_ENCODE_CHARS.matchesAnyOf(value)) {
103             value = parsePercentEncodeChars(value);
104         }
105         path.append(value);
106     }
107
108     private static void prepareNodeWithPredicates(final StringBuilder path, final PathArgument arg) {
109         path.append(arg.getNodeType().getLocalName());
110
111         final Iterator<Entry<QName, Object>> iterator = ((NodeIdentifierWithPredicates) arg).getKeyValues()
112                 .entrySet().iterator();
113
114         if (iterator.hasNext()) {
115             path.append(ParserBuilderConstants.Deserializer.EQUAL);
116         }
117
118         while (iterator.hasNext()) {
119             String valueOf = String.valueOf(iterator.next().getValue());
120             if (Serializer.PERCENT_ENCODE_CHARS.matchesAnyOf(valueOf)) {
121                 valueOf = parsePercentEncodeChars(valueOf);
122             }
123             path.append(valueOf);
124             if (iterator.hasNext()) {
125                 path.append(ParserBuilderConstants.Deserializer.COMMA);
126             }
127         }
128     }
129
130     /**
131      * Encode {@link Serializer#DISABLED_CHARS} chars to percent encoded chars
132      *
133      * @param valueOf
134      *            - string to encode
135      * @return encoded {@link String}
136      */
137     private static String parsePercentEncodeChars(final String valueOf) {
138         final StringBuilder sb = new StringBuilder();
139         int start = 0;
140         while (start < valueOf.length()) {
141             if (Serializer.PERCENT_ENCODE_CHARS.matches(valueOf.charAt(start))) {
142                 final String format = String.format("%x", (int) valueOf.charAt(start));
143                 final String upperCase = format.toUpperCase();
144                 sb.append(ParserBuilderConstants.Deserializer.PERCENT_ENCODING + upperCase);
145             } else {
146                 sb.append(valueOf.charAt(start));
147             }
148             start++;
149         }
150         return sb.toString();
151     }
152
153     /**
154      * Add {@link QName} to the serialized string
155      *
156      * @param path
157      *            - {@link StringBuilder}
158      * @param qname
159      *            - {@link QName} node
160      * @return {@link StringBuilder}
161      */
162     private final static StringBuilder appendQName(final StringBuilder path, final QName qname) {
163         path.append(qname.getLocalName());
164         return path;
165     }
166
167     /**
168      * Create prefix of namespace from {@link QName}
169      *
170      * @param qname
171      *            - {@link QName}
172      * @return {@link String}
173      */
174     private static String prefixForNamespace(final QName qname, final SchemaContext schemaContext) {
175         final URI namespace = qname.getNamespace();
176         Preconditions.checkArgument(namespace != null, "Failed to map QName {}", qname);
177         final Module module = schemaContext.findModuleByNamespaceAndRevision(namespace, qname.getRevision());
178         return module.getName();
179     }
180
181     private static final class MainVarsWrapper {
182
183         private DataSchemaContextNode<?> current;
184
185         public MainVarsWrapper(final DataSchemaContextNode<?> current) {
186             this.setCurrent(current);
187         }
188
189         public DataSchemaContextNode<?> getCurrent() {
190             return this.current;
191         }
192
193         public void setCurrent(final DataSchemaContextNode<?> current) {
194             this.current = current;
195         }
196
197     }
198 }