552fc3462d3c9ca8b924b29e21bbd762871d8fa6
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / nb / rfc8040 / utils / parser / AbstractFieldsTranslator.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.nb.rfc8040.utils.parser;
9
10 import java.util.ArrayList;
11 import java.util.HashSet;
12 import java.util.List;
13 import java.util.Optional;
14 import java.util.Set;
15 import org.eclipse.jdt.annotation.NonNull;
16 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
17 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
18 import org.opendaylight.restconf.nb.rfc8040.FieldsParam;
19 import org.opendaylight.restconf.nb.rfc8040.FieldsParam.NodeSelector;
20 import org.opendaylight.yangtools.yang.common.ErrorTag;
21 import org.opendaylight.yangtools.yang.common.ErrorType;
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.EffectiveModelContext;
27
28 /**
29  * Utilities used for parsing of fields query parameter content.
30  *
31  * @param <T> type of identifier
32  */
33 public abstract class AbstractFieldsTranslator<T> {
34     AbstractFieldsTranslator() {
35         // Hidden on purpose
36     }
37
38     /**
39      * Parse fields parameter and return complete list of child nodes organized into levels.
40      *
41      * @param identifier identifier context created from request URI
42      * @param input input value of fields parameter
43      * @return {@link List} of levels; each level contains {@link Set} of identifiers of type {@link T}
44      */
45     protected final @NonNull List<Set<T>> parseFields(final @NonNull InstanceIdentifierContext identifier,
46                                                       final @NonNull FieldsParam input) {
47         final DataSchemaContextNode<?> startNode = DataSchemaContextNode.fromDataSchemaNode(
48                 (DataSchemaNode) identifier.getSchemaNode());
49
50         if (startNode == null) {
51             throw new RestconfDocumentedException(
52                     "Start node missing in " + input, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
53         }
54
55         final List<Set<T>> parsed = new ArrayList<>();
56         processSelectors(parsed, identifier.getSchemaContext(), identifier.getSchemaNode().getQName().getModule(),
57             startNode, input.nodeSelectors());
58         return parsed;
59     }
60
61     /**
62      * Add parsed child of current node to result for current level.
63      *
64      * @param currentNode current node
65      * @param childQName parsed identifier of child node
66      * @param level current nodes level
67      * @return {@link DataSchemaContextNode}
68      */
69     protected abstract @NonNull DataSchemaContextNode<?> addChildToResult(@NonNull DataSchemaContextNode<?> currentNode,
70             @NonNull QName childQName, @NonNull Set<T> level);
71
72     private void processSelectors(final List<Set<T>> parsed, final EffectiveModelContext context,
73             final QNameModule startNamespace, final DataSchemaContextNode<?> startNode,
74             final List<NodeSelector> selectors) {
75         final Set<T> startLevel = new HashSet<>();
76         parsed.add(startLevel);
77
78         for (var selector : selectors) {
79             var node = startNode;
80             var namespace = startNamespace;
81             var level = startLevel;
82
83
84             // Note: path is guaranteed to have at least one step
85             final var it = selector.path().iterator();
86             while (true) {
87                 // FIXME: The layout of this loop is rather weird, which is due to how prepareQNameLevel() operates. We
88                 //        need to call it only when we know there is another identifier coming, otherwise we would end
89                 //        up with empty levels sneaking into the mix.
90                 //
91                 //        Dealing with that weirdness requires understanding what the expected end results are and a
92                 //        larger rewrite of the algorithms involved.
93                 final var step = it.next();
94                 final var module = step.module();
95                 if (module != null) {
96                     // FIXME: this is not defensive enough, as we can fail to find the module
97                     namespace = context.findModules(module).iterator().next().getQNameModule();
98                 }
99
100                 // add parsed identifier to results for current level
101                 node = addChildToResult(node, step.identifier().bindTo(namespace), level);
102                 if (!it.hasNext()) {
103                     break;
104                 }
105
106                 // go one level down
107                 level = prepareQNameLevel(parsed, level);
108             }
109
110             final var subs = selector.subSelectors();
111             if (!subs.isEmpty()) {
112                 processSelectors(parsed, context, namespace, node, subs);
113             }
114         }
115     }
116
117     /**
118      * Preparation of the identifiers level that is used as storage for parsed identifiers. If the current level exist
119      * at the index that doesn't equal to the last index of already parsed identifiers, a new level of identifiers
120      * is allocated and pushed to input parsed identifiers.
121      *
122      * @param parsedIdentifiers Already parsed list of identifiers grouped to multiple levels.
123      * @param currentLevel Current level of identifiers (set).
124      * @return Existing or new level of identifiers.
125      */
126     private Set<T> prepareQNameLevel(final List<Set<T>> parsedIdentifiers, final Set<T> currentLevel) {
127         final Optional<Set<T>> existingLevel = parsedIdentifiers.stream()
128                 .filter(qNameSet -> qNameSet.equals(currentLevel))
129                 .findAny();
130         if (existingLevel.isPresent()) {
131             final int index = parsedIdentifiers.indexOf(existingLevel.get());
132             if (index == parsedIdentifiers.size() - 1) {
133                 final Set<T> nextLevel = new HashSet<>();
134                 parsedIdentifiers.add(nextLevel);
135                 return nextLevel;
136             }
137
138             return parsedIdentifiers.get(index + 1);
139         }
140
141         final Set<T> nextLevel = new HashSet<>();
142         parsedIdentifiers.add(nextLevel);
143         return nextLevel;
144     }
145 }