/* * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved. * Copyright (c) 2021 PANTHEON.tech, s.r.o. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.restconf.nb.rfc8040.utils.parser; import java.util.List; import java.util.Set; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.restconf.api.query.FieldsParam; import org.opendaylight.restconf.common.context.InstanceIdentifierContext; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.nb.rfc8040.jersey.providers.ParameterAwareNormalizedNodeWriter; import org.opendaylight.yangtools.yang.common.ErrorTag; import org.opendaylight.yangtools.yang.common.ErrorType; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode; /** * Fields parser that stores set of {@link QName}s in each level. Because of this fact, from the output * it is is only possible to assume on what depth the selected element is placed. Identifiers of intermediary * mixin nodes are also flatten to the same level as identifiers of data nodes.
* Example: field 'a(/b/c);d/e' ('e' is place under choice node 'x') is parsed into following levels:
*
 * level 0: ['a', 'd']
 * level 1: ['b', 'x', 'e']
 * level 2: ['c']
 * 
*/ public final class WriterFieldsTranslator extends AbstractFieldsTranslator { private static final WriterFieldsTranslator INSTANCE = new WriterFieldsTranslator(); private WriterFieldsTranslator() { // Hidden on purpose } /** * Translate a {@link FieldsParam} to a complete list of child nodes organized into levels, suitable for use with * {@link ParameterAwareNormalizedNodeWriter}. * * @param identifier identifier context created from request URI * @param input input value of fields parameter * @return {@link List} of levels; each level contains set of {@link QName} */ public static @NonNull List> translate(final @NonNull InstanceIdentifierContext identifier, final @NonNull FieldsParam input) { return INSTANCE.parseFields(identifier, input); } @Override protected DataSchemaContextNode addChildToResult(final DataSchemaContextNode currentNode, final QName childQName, final Set level) { // resolve parent node final DataSchemaContextNode parentNode = resolveMixinNode( currentNode, level, currentNode.getIdentifier().getNodeType()); if (parentNode == null) { throw new RestconfDocumentedException( "Not-mixin node missing in " + currentNode.getIdentifier().getNodeType().getLocalName(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE); } // resolve child node final DataSchemaContextNode childNode = resolveMixinNode( parentNode.getChild(childQName), level, childQName); if (childNode == null) { throw new RestconfDocumentedException( "Child " + childQName.getLocalName() + " node missing in " + currentNode.getIdentifier().getNodeType().getLocalName(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE); } // add final childNode node to level nodes level.add(childNode.getIdentifier().getNodeType()); return childNode; } /** * Resolve mixin node by searching for inner nodes until not mixin node or null is found. * All nodes expect of not mixin node are added to current level nodes. * * @param node initial mixin or not-mixin node * @param level current nodes level * @param qualifiedName qname of initial node * @return {@link DataSchemaContextNode} */ private static @Nullable DataSchemaContextNode resolveMixinNode( final @Nullable DataSchemaContextNode node, final @NonNull Set level, final @NonNull QName qualifiedName) { DataSchemaContextNode currentNode = node; while (currentNode != null && currentNode.isMixin()) { level.add(qualifiedName); currentNode = currentNode.getChild(qualifiedName); } return currentNode; } }