Reduce exception guard
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / nb / rfc8040 / utils / parser / WriterFieldsTranslator.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  * Copyright (c) 2021 PANTHEON.tech, s.r.o.
4  *
5  * This program and the accompanying materials are made available under the
6  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
7  * and is available at http://www.eclipse.org/legal/epl-v10.html
8  */
9 package org.opendaylight.restconf.nb.rfc8040.utils.parser;
10
11 import java.util.List;
12 import java.util.Set;
13 import org.eclipse.jdt.annotation.NonNull;
14 import org.eclipse.jdt.annotation.Nullable;
15 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
16 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
17 import org.opendaylight.restconf.nb.rfc8040.FieldsParam;
18 import org.opendaylight.restconf.nb.rfc8040.jersey.providers.ParameterAwareNormalizedNodeWriter;
19 import org.opendaylight.yangtools.yang.common.ErrorTag;
20 import org.opendaylight.yangtools.yang.common.ErrorType;
21 import org.opendaylight.yangtools.yang.common.QName;
22 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
23
24 /**
25  * Fields parser that stores set of {@link QName}s in each level. Because of this fact, from the output
26  * it is is only possible to assume on what depth the selected element is placed. Identifiers of intermediary
27  * mixin nodes are also flatten to the same level as identifiers of data nodes.<br>
28  * Example: field 'a(/b/c);d/e' ('e' is place under choice node 'x') is parsed into following levels:<br>
29  * <pre>
30  * level 0: ['a', 'd']
31  * level 1: ['b', 'x', 'e']
32  * level 2: ['c']
33  * </pre>
34  */
35 public final class WriterFieldsTranslator extends AbstractFieldsTranslator<QName> {
36     private static final WriterFieldsTranslator INSTANCE = new WriterFieldsTranslator();
37
38     private WriterFieldsTranslator() {
39         // Hidden on purpose
40     }
41
42     /**
43      * Translate a {@link FieldsParam} to a complete list of child nodes organized into levels, suitable for use with
44      * {@link ParameterAwareNormalizedNodeWriter}.
45      *
46      * @param identifier identifier context created from request URI
47      * @param input input value of fields parameter
48      * @return {@link List} of levels; each level contains set of {@link QName}
49      */
50     public static @NonNull List<Set<QName>> translate(final @NonNull InstanceIdentifierContext identifier,
51                                                       final @NonNull FieldsParam input) {
52         return INSTANCE.parseFields(identifier, input);
53     }
54
55     @Override
56     protected DataSchemaContextNode<?> addChildToResult(final DataSchemaContextNode<?> currentNode,
57             final QName childQName, final Set<QName> level) {
58         // resolve parent node
59         final DataSchemaContextNode<?> parentNode = resolveMixinNode(
60                 currentNode, level, currentNode.getIdentifier().getNodeType());
61         if (parentNode == null) {
62             throw new RestconfDocumentedException(
63                     "Not-mixin node missing in " + currentNode.getIdentifier().getNodeType().getLocalName(),
64                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
65         }
66
67         // resolve child node
68         final DataSchemaContextNode<?> childNode = resolveMixinNode(
69                 parentNode.getChild(childQName), level, childQName);
70         if (childNode == null) {
71             throw new RestconfDocumentedException(
72                     "Child " + childQName.getLocalName() + " node missing in "
73                             + currentNode.getIdentifier().getNodeType().getLocalName(),
74                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
75         }
76
77         // add final childNode node to level nodes
78         level.add(childNode.getIdentifier().getNodeType());
79         return childNode;
80     }
81
82     /**
83      * Resolve mixin node by searching for inner nodes until not mixin node or null is found.
84      * All nodes expect of not mixin node are added to current level nodes.
85      *
86      * @param node          initial mixin or not-mixin node
87      * @param level         current nodes level
88      * @param qualifiedName qname of initial node
89      * @return {@link DataSchemaContextNode}
90      */
91     private static @Nullable DataSchemaContextNode<?> resolveMixinNode(
92             final @Nullable DataSchemaContextNode<?> node, final @NonNull Set<QName> level,
93             final @NonNull QName qualifiedName) {
94         DataSchemaContextNode<?> currentNode = node;
95         while (currentNode != null && currentNode.isMixin()) {
96             level.add(qualifiedName);
97             currentNode = currentNode.getChild(qualifiedName);
98         }
99
100         return currentNode;
101     }
102 }