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
8 package org.opendaylight.restconf.nb.rfc8040.utils.parser;
10 import java.util.ArrayList;
11 import java.util.HashSet;
12 import java.util.List;
13 import java.util.Optional;
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;
29 * Utilities used for parsing of fields query parameter content.
31 * @param <T> type of identifier
33 public abstract class AbstractFieldsTranslator<T> {
34 AbstractFieldsTranslator() {
39 * Parse fields parameter and return complete list of child nodes organized into levels.
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}
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());
50 if (startNode == null) {
51 throw new RestconfDocumentedException(
52 "Start node missing in " + input, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
55 final List<Set<T>> parsed = new ArrayList<>();
56 processSelectors(parsed, identifier.getSchemaContext(), identifier.getSchemaNode().getQName().getModule(),
57 startNode, input.nodeSelectors(), 0);
62 * Add parsed child of current node to result for current level.
64 * @param currentNode current node
65 * @param childQName parsed identifier of child node
66 * @param level current nodes level
67 * @return {@link DataSchemaContextNode}
69 protected abstract @NonNull DataSchemaContextNode<?> addChildToResult(@NonNull DataSchemaContextNode<?> currentNode,
70 @NonNull QName childQName, @NonNull Set<T> level);
72 private void processSelectors(final List<Set<T>> parsed, final EffectiveModelContext context,
73 final QNameModule startNamespace, final DataSchemaContextNode<?> startNode,
74 final List<NodeSelector> selectors, final int index) {
75 final Set<T> startLevel;
76 if (parsed.size() <= index) {
77 startLevel = new HashSet<>();
78 parsed.add(startLevel);
80 startLevel = parsed.get(index);
82 for (var selector : selectors) {
84 var namespace = startNamespace;
85 var level = startLevel;
86 var levelIndex = index;
88 // Note: path is guaranteed to have at least one step
89 final var it = selector.path().iterator();
91 // FIXME: The layout of this loop is rather weird, which is due to how prepareQNameLevel() operates. We
92 // need to call it only when we know there is another identifier coming, otherwise we would end
93 // up with empty levels sneaking into the mix.
95 // Dealing with that weirdness requires understanding what the expected end results are and a
96 // larger rewrite of the algorithms involved.
97 final var step = it.next();
98 final var module = step.module();
100 // FIXME: this is not defensive enough, as we can fail to find the module
101 namespace = context.findModules(module).iterator().next().getQNameModule();
104 // add parsed identifier to results for current level
105 node = addChildToResult(node, step.identifier().bindTo(namespace), level);
111 level = prepareQNameLevel(parsed, level);
115 final var subs = selector.subSelectors();
116 if (!subs.isEmpty()) {
117 processSelectors(parsed, context, namespace, node, subs, levelIndex + 1);
123 * Preparation of the identifiers level that is used as storage for parsed identifiers. If the current level exist
124 * at the index that doesn't equal to the last index of already parsed identifiers, a new level of identifiers
125 * is allocated and pushed to input parsed identifiers.
127 * @param parsedIdentifiers Already parsed list of identifiers grouped to multiple levels.
128 * @param currentLevel Current level of identifiers (set).
129 * @return Existing or new level of identifiers.
131 private Set<T> prepareQNameLevel(final List<Set<T>> parsedIdentifiers, final Set<T> currentLevel) {
132 final Optional<Set<T>> existingLevel = parsedIdentifiers.stream()
133 .filter(qNameSet -> qNameSet.equals(currentLevel))
135 if (existingLevel.isPresent()) {
136 final int index = parsedIdentifiers.indexOf(existingLevel.get());
137 if (index == parsedIdentifiers.size() - 1) {
138 final Set<T> nextLevel = new HashSet<>();
139 parsedIdentifiers.add(nextLevel);
143 return parsedIdentifiers.get(index + 1);
146 final Set<T> nextLevel = new HashSet<>();
147 parsedIdentifiers.add(nextLevel);