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
9 package org.opendaylight.restconf.utils.parser;
11 import java.util.ArrayList;
12 import java.util.HashSet;
13 import java.util.List;
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;
31 * @deprecated move to splitted module restconf-nb-rfc8040
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 = ')';
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}
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());
56 if (startNode == null) {
57 throw new RestconfDocumentedException(
58 "Start node missing in " + input, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
61 parseInput(input, startQNameModule, startNode, parsed, context);
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
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;
83 Set<QName> currentLevel = new HashSet<>();
84 parsed.add(currentLevel);
86 while (currentPosition < input.length()) {
87 final char currentChar = input.charAt(currentPosition);
89 if (Deserializer.IDENTIFIER.matches(currentChar) || currentChar == '/') {
90 if (currentChar == SLASH) {
91 // add parsed identifier to results for current level
92 currentNode = addChildToResult(
94 input.substring(startPosition, currentPosition), currentQNameModule, currentLevel);
96 currentLevel = new HashSet<>();
97 parsed.add(currentLevel);
100 startPosition = currentPosition;
108 switch (currentChar) {
110 // new namespace and revision found
111 currentQNameModule = context.findModuleByName(
112 input.substring(startPosition, currentPosition), null).getQNameModule();
115 case STARTING_PARENTHESIS:
116 // add current child to parsed results for current level
117 final DataSchemaContextNode<?> child = addChildToResult(
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));
124 input.substring(currentPosition + 1, closingParenthesis),
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) {
137 throw new RestconfDocumentedException(
138 "Missing semicolon character after "
139 + child.getIdentifier().getNodeType().getLocalName()
141 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
144 throw new RestconfDocumentedException(
145 "Unexpected character '"
146 + input.charAt(currentPosition)
147 + "' found in fields parameter value",
148 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
154 // complete identifier found
157 input.substring(startPosition, currentPosition), currentQNameModule, currentLevel);
161 throw new RestconfDocumentedException(
162 "Unexpected character '" + currentChar + "' found in fields parameter value",
163 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
166 startPosition = currentPosition;
169 // parse input to end
170 if (startPosition < input.length()) {
171 addChildToResult(currentNode, input.substring(startPosition), currentQNameModule, currentLevel);
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}
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(),
192 currentQNameModule.getRevision());
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);
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);
213 // add final childNode node to level nodes
214 level.add(childNode.getIdentifier().getNodeType());
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}
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);
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
244 private static int findClosingParenthesis(@Nonnull final String input) {
248 while (position < input.length()) {
249 final char currentChar = input.charAt(position);
251 if (currentChar == STARTING_PARENTHESIS) {
255 if (currentChar == CLOSING_PARENTHESIS) {
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);