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.netconf.sal.restconf.impl.InstanceIdentifierContext;
18 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
19 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
20 import org.opendaylight.netconf.sal.restconf.impl.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;
28 public class ParserFieldsParameter {
29 private static final char COLON = ':';
30 private static final char SEMICOLON = ';';
31 private static final char SLASH = '/';
32 private static final char STARTING_PARENTHESIS = '(';
33 private static final char CLOSING_PARENTHESIS = ')';
36 * Parse fields parameter and return complete list of child nodes organized into levels.
37 * @param identifier identifier context created from request URI
38 * @param input input value of fields parameter
39 * @return {@link List}
41 public static @Nonnull List<Set<QName>> parseFieldsParameter(@Nonnull final InstanceIdentifierContext<?> identifier,
42 @Nonnull final String input) {
43 final List<Set<QName>> parsed = new ArrayList<>();
44 final SchemaContext context = identifier.getSchemaContext();
45 final QNameModule startQNameModule = identifier.getSchemaNode().getQName().getModule();
46 final DataSchemaContextNode<?> startNode = DataSchemaContextNode.fromDataSchemaNode(
47 (DataSchemaNode) identifier.getSchemaNode());
49 if (startNode == null) {
50 throw new RestconfDocumentedException(
51 "Start node missing in " + input, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
54 parseInput(input, startQNameModule, startNode, parsed, context);
59 * Parse input value of fields parameter and create list of sets. Each set represents one level of child nodes.
60 * @param input input value of fields parameter
61 * @param startQNameModule starting qname module
62 * @param startNode starting node
63 * @param parsed list of results
64 * @param context schema context
66 private static void parseInput(@Nonnull final String input,
67 @Nonnull final QNameModule startQNameModule,
68 @Nonnull final DataSchemaContextNode<?> startNode,
69 @Nonnull final List<Set<QName>> parsed,
70 @Nonnull final SchemaContext context) {
71 int currentPosition = 0;
72 int startPosition = 0;
73 DataSchemaContextNode<?> currentNode = startNode;
74 QNameModule currentQNameModule = startQNameModule;
76 Set<QName> currentLevel = new HashSet<>();
77 parsed.add(currentLevel);
79 while (currentPosition < input.length()) {
80 final char currentChar = input.charAt(currentPosition);
82 if (Deserializer.IDENTIFIER.matches(currentChar) || currentChar == '/') {
83 if (currentChar == SLASH) {
84 // add parsed identifier to results for current level
85 currentNode = addChildToResult(
87 input.substring(startPosition, currentPosition), currentQNameModule, currentLevel);
89 currentLevel = new HashSet<>();
90 parsed.add(currentLevel);
93 startPosition = currentPosition;
101 switch (currentChar) {
103 // new namespace and revision found
104 currentQNameModule = context.findModuleByName(
105 input.substring(startPosition, currentPosition), null).getQNameModule();
108 case STARTING_PARENTHESIS:
109 // add current child to parsed results for current level
110 final DataSchemaContextNode<?> child = addChildToResult(
112 input.substring(startPosition, currentPosition), currentQNameModule, currentLevel);
113 // call with child node as new start node for one level down
114 int closingParenthesis = currentPosition
115 + findClosingParenthesis(input.substring(currentPosition + 1));
117 input.substring(currentPosition + 1, closingParenthesis),
123 // closing parenthesis must be at the end of input or separator and one more character is expected
124 currentPosition = closingParenthesis + 1;
125 if (currentPosition != input.length()) {
126 if (currentPosition + 1 < input.length()) {
127 if (input.charAt(currentPosition) == SEMICOLON) {
130 throw new RestconfDocumentedException(
131 "Missing semicolon character after "
132 + child.getIdentifier().getNodeType().getLocalName()
134 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
137 throw new RestconfDocumentedException(
138 "Unexpected character '"
139 + input.charAt(currentPosition)
140 + "' found in fields parameter value",
141 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
147 // complete identifier found
150 input.substring(startPosition, currentPosition), currentQNameModule, currentLevel);
154 throw new RestconfDocumentedException(
155 "Unexpected character '" + currentChar + "' found in fields parameter value",
156 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
159 startPosition = currentPosition;
162 // parse input to end
163 if (startPosition < input.length()) {
164 addChildToResult(currentNode, input.substring(startPosition), currentQNameModule, currentLevel);
169 * Add parsed child of current node to result for current level.
170 * @param currentNode current node
171 * @param identifier parsed identifier of child node
172 * @param currentQNameModule current namespace and revision in {@link QNameModule}
173 * @param level current nodes level
174 * @return {@link DataSchemaContextNode}
176 private static @Nonnull DataSchemaContextNode<?> addChildToResult(
177 @Nonnull final DataSchemaContextNode<?> currentNode,
178 @Nonnull final String identifier,
179 @Nonnull final QNameModule currentQNameModule,
180 @Nonnull final Set<QName> level) {
181 final QName childQName = QName.create(
182 currentQNameModule.getNamespace().toString(),
184 currentQNameModule.getRevision());
186 // resolve parent node
187 final DataSchemaContextNode<?> parentNode = resolveMixinNode(
188 currentNode, level, currentNode.getIdentifier().getNodeType());
189 if (parentNode == null) {
190 throw new RestconfDocumentedException(
191 "Not-mixin node missing in " + currentNode.getIdentifier().getNodeType().getLocalName(),
192 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
195 // resolve child node
196 final DataSchemaContextNode<?> childNode = resolveMixinNode(
197 parentNode.getChild(childQName), level, childQName);
198 if (childNode == null) {
199 throw new RestconfDocumentedException(
200 "Child " + identifier + " node missing in "
201 + currentNode.getIdentifier().getNodeType().getLocalName(),
202 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
205 // add final childNode node to level nodes
206 level.add(childNode.getIdentifier().getNodeType());
211 * Resolve mixin node by searching for inner nodes until not mixin node or null is found.
212 * All nodes expect of not mixin node are added to current level nodes.
213 * @param node initial mixin or not-mixin node
214 * @param level current nodes level
215 * @param qName qname of initial node
216 * @return {@link DataSchemaContextNode}
218 private static @Nullable DataSchemaContextNode<?> resolveMixinNode(@Nullable final DataSchemaContextNode<?> node,
219 @Nonnull final Set<QName> level,
220 @Nonnull final QName qName) {
221 DataSchemaContextNode<?> currentNode = node;
222 while (currentNode != null && currentNode.isMixin()) {
224 currentNode = currentNode.getChild(qName);
231 * Find position of matching parenthesis increased by one, but at most equals to input size.
232 * @param input input where to find for closing parenthesis
233 * @return int position of closing parenthesis increased by one
235 private static int findClosingParenthesis(@Nonnull final String input) {
239 while (position < input.length()) {
240 final char currentChar = input.charAt(position);
242 if (currentChar == STARTING_PARENTHESIS) {
246 if (currentChar == CLOSING_PARENTHESIS) {
257 // closing parenthesis was not found
258 if (position >= input.length()) {
259 throw new RestconfDocumentedException("Missing closing parenthesis in fields parameter",
260 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);