Check rc:yang-data template name duplications
[yangtools.git] / parser / rfc8040-parser-support / src / main / java / org / opendaylight / yangtools / rfc8040 / parser / YangDataStatementSupport.java
1 /*
2  * Copyright (c) 2017 Pantheon Technologies, s.r.o. 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.yangtools.rfc8040.parser;
9
10 import static com.google.common.base.Verify.verify;
11
12 import com.google.common.annotations.Beta;
13 import com.google.common.annotations.VisibleForTesting;
14 import com.google.common.collect.ImmutableList;
15 import java.util.stream.Collectors;
16 import org.eclipse.jdt.annotation.NonNull;
17 import org.opendaylight.yangtools.rfc8040.model.api.YangDataConstants;
18 import org.opendaylight.yangtools.rfc8040.model.api.YangDataEffectiveStatement;
19 import org.opendaylight.yangtools.rfc8040.model.api.YangDataStatement;
20 import org.opendaylight.yangtools.rfc8040.model.api.YangDataStatements;
21 import org.opendaylight.yangtools.yang.common.YangDataName;
22 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
23 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
24 import org.opendaylight.yangtools.yang.model.api.meta.DeclarationReference;
25 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
26 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
27 import org.opendaylight.yangtools.yang.model.api.stmt.ChoiceEffectiveStatement;
28 import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeEffectiveStatement;
29 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
30 import org.opendaylight.yangtools.yang.model.api.stmt.UsesEffectiveStatement;
31 import org.opendaylight.yangtools.yang.parser.api.YangParserConfiguration;
32 import org.opendaylight.yangtools.yang.parser.spi.meta.AbstractStatementSupport;
33 import org.opendaylight.yangtools.yang.parser.spi.meta.BoundStmtCtx;
34 import org.opendaylight.yangtools.yang.parser.spi.meta.EffectiveStmtCtx.Current;
35 import org.opendaylight.yangtools.yang.parser.spi.meta.InvalidSubstatementException;
36 import org.opendaylight.yangtools.yang.parser.spi.meta.MissingSubstatementException;
37 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour;
38 import org.opendaylight.yangtools.yang.parser.spi.meta.ParserNamespace;
39 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
40 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable;
41 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
42 import org.opendaylight.yangtools.yang.parser.spi.meta.SubstatementValidator;
43 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
44
45 @Beta
46 public final class YangDataStatementSupport
47         extends AbstractStatementSupport<YangDataName, YangDataStatement, YangDataEffectiveStatement> {
48     private static final @NonNull ParserNamespace<YangDataName,
49         StmtContext<YangDataName, YangDataStatement, YangDataEffectiveStatement>> NAMESPACE =
50         new ParserNamespace<>("yang-data");
51     public static final @NonNull NamespaceBehaviour<YangDataName,
52         StmtContext<YangDataName, YangDataStatement, YangDataEffectiveStatement>> BEHAVIOUR =
53         NamespaceBehaviour.global(NAMESPACE);
54
55     // As per RFC8040 page 81:
56     //
57     //    The substatements of this extension MUST follow the
58     //    'data-def-stmt' rule in the YANG ABNF.
59     //
60     // As per RFC7950 page 185:
61     //
62     //    data-def-stmt = container-stmt /
63     //                    leaf-stmt /
64     //                    leaf-list-stmt /
65     //                    list-stmt /
66     //                    choice-stmt /
67     //                    anydata-stmt /
68     //                    anyxml-stmt /
69     //                    uses-stmt
70     //
71     // The cardinality is not exactly constrained, but the entirety of substatements are required to resolve to a single
72     // XML document (page 80). This is enforced when we arrive at full declaration.
73     private static final SubstatementValidator VALIDATOR = SubstatementValidator.builder(YangDataStatements.YANG_DATA)
74         .addAny(YangStmtMapping.CONTAINER)
75         .addAny(YangStmtMapping.LEAF)
76         .addAny(YangStmtMapping.LEAF_LIST)
77         .addAny(YangStmtMapping.LIST)
78         .addAny(YangStmtMapping.CHOICE)
79         .addAny(YangStmtMapping.ANYDATA)
80         .addAny(YangStmtMapping.ANYXML)
81         .addAny(YangStmtMapping.USES)
82         .build();
83
84     @VisibleForTesting
85     static final YangDataName YANG_API = new YangDataName(YangDataConstants.RFC8040_MODULE, "yang-api");
86
87     public YangDataStatementSupport(final YangParserConfiguration config) {
88         super(YangDataStatements.YANG_DATA, StatementPolicy.reject(), config, VALIDATOR);
89     }
90
91     @Override
92     public YangDataName parseArgumentValue(final StmtContext<?, ?, ?> ctx, final String value) {
93         try {
94             return new YangDataName(StmtContextUtils.getModuleQName(ctx.getRoot()), value);
95         } catch (IllegalArgumentException e) {
96             throw new SourceException(ctx, e, "Invalid yang-data argument %s", value);
97         }
98     }
99
100     @Override
101     public void onStatementAdded(final Mutable<YangDataName, YangDataStatement, YangDataEffectiveStatement> stmt) {
102         // as per https://www.rfc-editor.org/rfc/rfc8040#section-8,
103         // yang-data is ignored unless it appears as a top-level statement
104         final var parent = stmt.coerceParentContext();
105         if (parent.getParentContext() != null) {
106             stmt.setUnsupported();
107             return;
108         }
109
110         final var name = stmt.argument();
111         final var prev = parent.namespaceItem(NAMESPACE, name);
112         if (prev != null) {
113             throw new SourceException(stmt,
114                 "Error in module '%s': cannot add '%s'. Node name collision: '%s' already declared at %s",
115                 stmt.getRoot().rawArgument(), name, prev.argument(), prev.sourceReference());
116         }
117         parent.addToNs(NAMESPACE, stmt.argument(), stmt);
118     }
119
120     @Override
121     public void onFullDefinitionDeclared(
122             final Mutable<YangDataName, YangDataStatement, YangDataEffectiveStatement> ctx) {
123         // If we are declared in an illegal place, this becomes a no-op
124         if (!ctx.isSupportedToBuildEffective()) {
125             return;
126         }
127
128         // Run SubstatementValidator-based validation first
129         super.onFullDefinitionDeclared(ctx);
130
131         // Support for 'operations' container semantics. For this we need to recognize when the model at hand matches
132         // RFC8040 ietf-restconf module. In ordered to do that we hook onto this particular definition:
133         //
134         //   rc:yang-data yang-api {
135         //     uses restconf;
136         //   }
137         //
138         // If we find it, we hook an inference action which performs the next step when the module is fully declared.
139         if (YANG_API.equals(ctx.getArgument())) {
140             final var stmts = ctx.declaredSubstatements();
141             if (stmts.size() == 1) {
142                 final var stmt = stmts.iterator().next();
143                 if (stmt.producesEffective(UsesEffectiveStatement.class) && "restconf".equals(stmt.rawArgument())) {
144                     // The rc:yang-data shape matches, but we are not sure about the module identity, that needs to be
145                     // done later multiple stages, the first one being initiated through this call.
146                     OperationsValidateModuleAction.applyTo(ctx.coerceParentContext());
147                 }
148             }
149         }
150     }
151
152     @Override
153     public boolean isIgnoringIfFeatures() {
154         return true;
155     }
156
157     @Override
158     public boolean isIgnoringConfig() {
159         return true;
160     }
161
162     @Override
163     protected YangDataStatement createDeclared(final BoundStmtCtx<YangDataName> ctx,
164             final ImmutableList<DeclaredStatement<?>> substatements) {
165         return new YangDataStatementImpl(ctx.getArgument(), substatements);
166     }
167
168     @Override
169     protected YangDataStatement attachDeclarationReference(final YangDataStatement stmt,
170             final DeclarationReference reference) {
171         return new RefYangDataStatement(stmt, reference);
172     }
173
174     @Override
175     protected YangDataEffectiveStatement createEffective(final Current<YangDataName, YangDataStatement> stmt,
176             final ImmutableList<? extends EffectiveStatement<?, ?>> substatements) {
177         // RFC8040 page 80 requires that:
178         //    It MUST contain data definition statements
179         //    that result in exactly one container data node definition.
180         //    An instance of a YANG data template can thus be translated
181         //    into an XML instance document, whose top-level element
182         //    corresponds to the top-level container.
183         //
184         // We validate this additional constraint when we arrive at the effective model, with the view that
185         // 'container data node definition' is really meant to say 'XML element'.
186         //
187         // This really boils down to the requirement to have a single schema tree substatement, which needs to either
188         // be a data tree statement or a choice statement.
189         final var schemaSub = substatements.stream()
190             .filter(SchemaTreeEffectiveStatement.class::isInstance)
191             .map(SchemaTreeEffectiveStatement.class::cast)
192             .collect(Collectors.toUnmodifiableList());
193         final var child = switch (schemaSub.size()) {
194             case 0 -> throw new MissingSubstatementException(stmt, "yang-data requires at least one substatement");
195             case 1 -> {
196                 final SchemaTreeEffectiveStatement<?> substmt = schemaSub.get(0);
197                 SourceException.throwIf(
198                     !(substmt instanceof ChoiceEffectiveStatement) && !(substmt instanceof DataTreeEffectiveStatement),
199                     stmt, "%s is not a recognized container data node definition", substmt);
200                 verify(substmt instanceof DataSchemaNode, "Unexpected single child %s", substmt);
201                 yield (DataSchemaNode) substmt;
202             }
203             default -> throw new InvalidSubstatementException(stmt,
204                 "yang-data requires exactly one container data node definition, found %s", schemaSub);
205         };
206
207         return new YangDataEffectiveStatementImpl(stmt, substatements, child);
208     }
209 }