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