2ba3f045615748d6c6522d2f712bace95ebfabfc
[netconf.git] / restconf / restconf-nb-bierman02 / src / main / java / org / opendaylight / restconf / utils / parser / ParserFieldsParameter.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
9 package org.opendaylight.restconf.utils.parser;
10
11 import java.util.ArrayList;
12 import java.util.HashSet;
13 import java.util.List;
14 import java.util.Set;
15 import javax.annotation.Nonnull;
16 import javax.annotation.Nullable;
17 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
18 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
19 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
20 import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
21 import org.opendaylight.restconf.utils.parser.builder.ParserBuilderConstants.Deserializer;
22 import org.opendaylight.yangtools.yang.common.QName;
23 import org.opendaylight.yangtools.yang.common.QNameModule;
24 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
25 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
26 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
27
28 /**
29  * Parameters parser.
30  *
31  * @deprecated move to splitted module restconf-nb-rfc8040
32  */
33 @Deprecated
34 public class ParserFieldsParameter {
35     private static final char COLON = ':';
36     private static final char SEMICOLON = ';';
37     private static final char SLASH = '/';
38     private static final char STARTING_PARENTHESIS = '(';
39     private static final char CLOSING_PARENTHESIS = ')';
40
41     /**
42      * Parse fields parameter and return complete list of child nodes organized into levels.
43      * @param identifier identifier context created from request URI
44      * @param input input value of fields parameter
45      * @return {@link List}
46      */
47     @Nonnull
48     public static List<Set<QName>> parseFieldsParameter(@Nonnull final InstanceIdentifierContext<?> identifier,
49                                                         @Nonnull final String input) {
50         final List<Set<QName>> parsed = new ArrayList<>();
51         final SchemaContext context = identifier.getSchemaContext();
52         final QNameModule startQNameModule = identifier.getSchemaNode().getQName().getModule();
53         final DataSchemaContextNode<?> startNode = DataSchemaContextNode.fromDataSchemaNode(
54                 (DataSchemaNode) identifier.getSchemaNode());
55
56         if (startNode == null) {
57             throw new RestconfDocumentedException(
58                     "Start node missing in " + input, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
59         }
60
61         parseInput(input, startQNameModule, startNode, parsed, context);
62         return parsed;
63     }
64
65     /**
66      * Parse input value of fields parameter and create list of sets. Each set represents one level of child nodes.
67      * @param input input value of fields parameter
68      * @param startQNameModule starting qname module
69      * @param startNode starting node
70      * @param parsed list of results
71      * @param context schema context
72      */
73     private static void parseInput(@Nonnull final String input,
74                                    @Nonnull final QNameModule startQNameModule,
75                                    @Nonnull final DataSchemaContextNode<?> startNode,
76                                    @Nonnull final List<Set<QName>> parsed,
77                                    @Nonnull final SchemaContext context) {
78         int currentPosition = 0;
79         int startPosition = 0;
80         DataSchemaContextNode<?> currentNode = startNode;
81         QNameModule currentQNameModule = startQNameModule;
82
83         Set<QName> currentLevel = new HashSet<>();
84         parsed.add(currentLevel);
85
86         while (currentPosition < input.length()) {
87             final char currentChar = input.charAt(currentPosition);
88
89             if (Deserializer.IDENTIFIER.matches(currentChar) || currentChar == '/') {
90                 if (currentChar == SLASH) {
91                     // add parsed identifier to results for current level
92                     currentNode = addChildToResult(
93                             currentNode,
94                             input.substring(startPosition, currentPosition), currentQNameModule, currentLevel);
95                     // go one level down
96                     currentLevel = new HashSet<>();
97                     parsed.add(currentLevel);
98
99                     currentPosition++;
100                     startPosition = currentPosition;
101                 } else {
102                     currentPosition++;
103                 }
104
105                 continue;
106             }
107
108             switch (currentChar) {
109                 case COLON :
110                     // new namespace and revision found
111                     currentQNameModule = context.findModuleByName(
112                             input.substring(startPosition, currentPosition), null).getQNameModule();
113                     currentPosition++;
114                     break;
115                 case STARTING_PARENTHESIS:
116                     // add current child to parsed results for current level
117                     final DataSchemaContextNode<?> child = addChildToResult(
118                             currentNode,
119                             input.substring(startPosition, currentPosition), currentQNameModule, currentLevel);
120                     // call with child node as new start node for one level down
121                     final int closingParenthesis = currentPosition
122                             + findClosingParenthesis(input.substring(currentPosition + 1));
123                     parseInput(
124                             input.substring(currentPosition + 1, closingParenthesis),
125                             currentQNameModule,
126                             child,
127                             parsed,
128                             context);
129
130                     // closing parenthesis must be at the end of input or separator and one more character is expected
131                     currentPosition = closingParenthesis + 1;
132                     if (currentPosition != input.length()) {
133                         if (currentPosition + 1 < input.length()) {
134                             if (input.charAt(currentPosition) == SEMICOLON) {
135                                 currentPosition++;
136                             } else {
137                                 throw new RestconfDocumentedException(
138                                         "Missing semicolon character after "
139                                                 + child.getIdentifier().getNodeType().getLocalName()
140                                                 + " child nodes",
141                                         ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
142                             }
143                         } else {
144                             throw new RestconfDocumentedException(
145                                     "Unexpected character '"
146                                             + input.charAt(currentPosition)
147                                             + "' found in fields parameter value",
148                                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
149                         }
150                     }
151
152                     break;
153                 case SEMICOLON:
154                     // complete identifier found
155                     addChildToResult(
156                             currentNode,
157                             input.substring(startPosition, currentPosition), currentQNameModule, currentLevel);
158                     currentPosition++;
159                     break;
160                 default:
161                     throw new RestconfDocumentedException(
162                             "Unexpected character '" + currentChar + "' found in fields parameter value",
163                             ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
164             }
165
166             startPosition = currentPosition;
167         }
168
169         // parse input to end
170         if (startPosition < input.length()) {
171             addChildToResult(currentNode, input.substring(startPosition), currentQNameModule, currentLevel);
172         }
173     }
174
175     /**
176      * Add parsed child of current node to result for current level.
177      * @param currentNode current node
178      * @param identifier parsed identifier of child node
179      * @param currentQNameModule current namespace and revision in {@link QNameModule}
180      * @param level current nodes level
181      * @return {@link DataSchemaContextNode}
182      */
183     @Nonnull
184     private static DataSchemaContextNode<?> addChildToResult(
185             @Nonnull final DataSchemaContextNode<?> currentNode,
186             @Nonnull final String identifier,
187             @Nonnull final QNameModule currentQNameModule,
188             @Nonnull final Set<QName> level) {
189         final QName childQName = QName.create(
190                 currentQNameModule.getNamespace().toString(),
191                 identifier,
192                 currentQNameModule.getRevision());
193
194         // resolve parent node
195         final DataSchemaContextNode<?> parentNode = resolveMixinNode(
196                 currentNode, level, currentNode.getIdentifier().getNodeType());
197         if (parentNode == null) {
198             throw new RestconfDocumentedException(
199                     "Not-mixin node missing in " + currentNode.getIdentifier().getNodeType().getLocalName(),
200                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
201         }
202
203         // resolve child node
204         final DataSchemaContextNode<?> childNode = resolveMixinNode(
205                 parentNode.getChild(childQName), level, childQName);
206         if (childNode == null) {
207             throw new RestconfDocumentedException(
208                     "Child " + identifier + " node missing in "
209                             + currentNode.getIdentifier().getNodeType().getLocalName(),
210                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
211         }
212
213         // add final childNode node to level nodes
214         level.add(childNode.getIdentifier().getNodeType());
215         return childNode;
216     }
217
218     /**
219      * Resolve mixin node by searching for inner nodes until not mixin node or null is found.
220      * All nodes expect of not mixin node are added to current level nodes.
221      * @param node initial mixin or not-mixin node
222      * @param level current nodes level
223      * @param qualifiedName qname of initial node
224      * @return {@link DataSchemaContextNode}
225      */
226     @Nullable
227     private static DataSchemaContextNode<?> resolveMixinNode(@Nullable final DataSchemaContextNode<?> node,
228                                                              @Nonnull final Set<QName> level,
229                                                              @Nonnull final QName qualifiedName) {
230         DataSchemaContextNode<?> currentNode = node;
231         while (currentNode != null && currentNode.isMixin()) {
232             level.add(qualifiedName);
233             currentNode = currentNode.getChild(qualifiedName);
234         }
235
236         return currentNode;
237     }
238
239     /**
240      * Find position of matching parenthesis increased by one, but at most equals to input size.
241      * @param input input where to find for closing parenthesis
242      * @return int position of closing parenthesis increased by one
243      */
244     private static int findClosingParenthesis(@Nonnull final String input) {
245         int position = 0;
246         int count = 1;
247
248         while (position < input.length()) {
249             final char currentChar = input.charAt(position);
250
251             if (currentChar == STARTING_PARENTHESIS) {
252                 count++;
253             }
254
255             if (currentChar == CLOSING_PARENTHESIS) {
256                 count--;
257             }
258
259             if (count == 0) {
260                 break;
261             }
262
263             position++;
264         }
265
266         // closing parenthesis was not found
267         if (position >= input.length()) {
268             throw new RestconfDocumentedException("Missing closing parenthesis in fields parameter",
269                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
270         }
271
272         return ++position;
273     }
274 }