From: Claudio David Gasparini Date: Mon, 14 Dec 2020 08:49:13 +0000 (+0100) Subject: Introduce YangTextSchemaSourceSet X-Git-Tag: 0.0.1~84 X-Git-Url: https://gerrit.onap.org/r/gitweb?a=commitdiff_plain;h=0e9a8a51e51c7f3f90e4eae4b9907558b39818f8;p=cps.git Introduce YangTextSchemaSourceSet Common interface among all layers. YangTextSchemaSourceSet responsability is to provide all yang model required information of an YangSchema. Issue-ID: CPS-21 Signed-off-by: Claudio David Gasparini Change-Id: I9cba490dec25defbddbb3524c6d6c1535bee63bc --- diff --git a/cps-service/pom.xml b/cps-service/pom.xml index 3e8cc2deb..642d76451 100644 --- a/cps-service/pom.xml +++ b/cps-service/pom.xml @@ -12,6 +12,10 @@ cps-service + + org.opendaylight.yangtools + yang-model-api + org.opendaylight.yangtools yang-parser-api diff --git a/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java b/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java index 071ff6ad3..a35533c8f 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java +++ b/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java @@ -19,18 +19,23 @@ package org.onap.cps.utils; +import static com.google.common.base.Preconditions.checkArgument; +import static org.opendaylight.yangtools.yang.common.YangConstants.RFC6020_YANG_FILE_EXTENSION; + +import com.google.common.base.Charsets; +import com.google.common.io.Files; import com.google.gson.stream.JsonReader; import java.io.File; import java.io.IOException; import java.io.StringReader; import java.util.Collection; import java.util.Collections; -import java.util.Iterator; import java.util.List; -import java.util.ServiceLoader; import java.util.logging.Logger; import java.util.stream.Collectors; import org.onap.cps.api.impl.Fragment; +import org.onap.cps.yang.YangTextSchemaSourceSet; +import org.onap.cps.yang.YangTextSchemaSourceSetBuilder; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; @@ -48,44 +53,37 @@ import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeS import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult; import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.opendaylight.yangtools.yang.model.parser.api.YangParser; import org.opendaylight.yangtools.yang.model.parser.api.YangParserException; -import org.opendaylight.yangtools.yang.model.parser.api.YangParserFactory; -import org.opendaylight.yangtools.yang.model.repo.api.StatementParserMode; -import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; public class YangUtils { - - private static final YangParserFactory PARSER_FACTORY; - private static final Logger LOGGER = Logger.getLogger(YangUtils.class.getName()); private YangUtils() { throw new IllegalStateException("Utility class"); } - static { - final Iterator it = ServiceLoader.load(YangParserFactory.class).iterator(); - if (!it.hasNext()) { - throw new IllegalStateException("No YangParserFactory found"); - } - PARSER_FACTORY = it.next(); - } - /** * Parse a file containing yang modules. * - * @param yangModelFile a file containing one or more yang modules. The file has to have a .yang extension. + * @param yangModelFiles list of files containing one or more yang modules. The file has to have a .yang extension. * @return a SchemaContext representing the yang model * @throws IOException when the system as an IO issue * @throws YangParserException when the file does not contain a valid yang structure */ - public static SchemaContext parseYangModelFile(final File yangModelFile) throws IOException, YangParserException { - final YangTextSchemaSource yangTextSchemaSource = YangTextSchemaSource.forFile(yangModelFile); - final YangParser yangParser = PARSER_FACTORY - .createParser(StatementParserMode.DEFAULT_MODE); - yangParser.addSource(yangTextSchemaSource); - return yangParser.buildEffectiveModel(); + @Deprecated + public static YangTextSchemaSourceSet parseYangModelFiles(final List yangModelFiles) + throws IOException, YangParserException, ReactorException { + final YangTextSchemaSourceSetBuilder yangModelsMapBuilder = new YangTextSchemaSourceSetBuilder(); + for (final File file :yangModelFiles) { + final String fileNameWithExtension = file.getName(); + checkArgument(fileNameWithExtension.endsWith(RFC6020_YANG_FILE_EXTENSION), + "Filename %s does not end with '%s'", RFC6020_YANG_FILE_EXTENSION, + fileNameWithExtension); + final String content = Files.asCharSource(file, Charsets.UTF_8).read(); + yangModelsMapBuilder.put(fileNameWithExtension, content); + } + return yangModelsMapBuilder.build(); } /** diff --git a/cps-service/src/main/java/org/onap/cps/yang/YangTextSchemaSourceSet.java b/cps-service/src/main/java/org/onap/cps/yang/YangTextSchemaSourceSet.java new file mode 100644 index 000000000..5c337770d --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/yang/YangTextSchemaSourceSet.java @@ -0,0 +1,35 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2020 Pantheon.tech + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.yang; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +/** + * CPS YangTextSchemaSource. + */ +public interface YangTextSchemaSourceSet { + /** + * Return SchemaContext for given YangSchema. + * @return SchemaContext + */ + @NonNull + SchemaContext getSchemaContext(); +} diff --git a/cps-service/src/main/java/org/onap/cps/yang/YangTextSchemaSourceSetBuilder.java b/cps-service/src/main/java/org/onap/cps/yang/YangTextSchemaSourceSetBuilder.java new file mode 100644 index 000000000..1588c44d9 --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/yang/YangTextSchemaSourceSetBuilder.java @@ -0,0 +1,122 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2020 Pantheon.tech + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.yang; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableMap; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.opendaylight.yangtools.yang.common.Revision; +import org.opendaylight.yangtools.yang.common.YangNames; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException; +import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier; +import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource; +import org.opendaylight.yangtools.yang.parser.rfc7950.reactor.RFC7950Reactors; +import org.opendaylight.yangtools.yang.parser.rfc7950.repo.YangStatementStreamSource; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; +import org.opendaylight.yangtools.yang.parser.stmt.reactor.CrossSourceStatementReactor; + +public final class YangTextSchemaSourceSetBuilder { + + private final ImmutableMap.Builder yangModelMap = new ImmutableMap.Builder<>(); + + public YangTextSchemaSourceSetBuilder() { + } + + public YangTextSchemaSourceSetBuilder put(final String fileName, final String content) { + this.yangModelMap.put(fileName, content); + return this; + } + + public YangTextSchemaSourceSetBuilder putAll(final Map yangResourceNameToContent) { + this.yangModelMap.putAll(yangResourceNameToContent); + return this; + } + + public YangTextSchemaSourceSet build() throws ReactorException, IOException, YangSyntaxErrorException { + final SchemaContext schemaContext = generateSchemaContext(yangModelMap.build()); + return new YangTextSchemaSourceSetImpl(schemaContext); + } + + public static YangTextSchemaSourceSet of(final Map yangResourceNameToContent) + throws ReactorException, IOException, YangSyntaxErrorException { + return new YangTextSchemaSourceSetBuilder().putAll(yangResourceNameToContent).build(); + } + + private static class YangTextSchemaSourceSetImpl implements YangTextSchemaSourceSet { + + private final SchemaContext schemaContext; + + public YangTextSchemaSourceSetImpl(final SchemaContext schemaContext) { + this.schemaContext = schemaContext; + } + + @Override + public SchemaContext getSchemaContext() { + return schemaContext; + } + } + + /** + * Parse and validate a string representing a yang model to generate a SchemaContext context. + * + * @param yangResourceNameToContent is a {@link Map} collection that contains the name of the model represented + * on yangModelContent as key and the yangModelContent as value. + * @return the schema context + */ + private SchemaContext generateSchemaContext(final Map yangResourceNameToContent) + throws IOException, ReactorException, YangSyntaxErrorException { + final CrossSourceStatementReactor.BuildAction reactor = RFC7950Reactors.defaultReactor().newBuild(); + final List yangTextSchemaSources = forResources(yangResourceNameToContent); + for (final YangTextSchemaSource yangTextSchemaSource : yangTextSchemaSources) { + reactor.addSource(YangStatementStreamSource.create(yangTextSchemaSource)); + } + return reactor.buildEffective(); + } + + private List forResources(final Map yangResourceNameToContent) { + return yangResourceNameToContent.entrySet().stream() + .map(entry -> toYangTextSchemaSource(entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); + } + + private YangTextSchemaSource toYangTextSchemaSource(final String sourceName, final String source) { + final Map.Entry sourceNameParsed = YangNames.parseFilename(sourceName); + final RevisionSourceIdentifier revisionSourceIdentifier = RevisionSourceIdentifier + .create(sourceNameParsed.getKey(), Revision.ofNullable(sourceNameParsed.getValue())); + return new YangTextSchemaSource(revisionSourceIdentifier) { + @Override + protected MoreObjects.ToStringHelper addToStringAttributes( + final MoreObjects.ToStringHelper toStringHelper) { + return toStringHelper; + } + + @Override + public InputStream openStream() { + return new ByteArrayInputStream(source.getBytes()); + } + }; + } +} diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy index 801e43079..e002b180a 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy @@ -33,7 +33,7 @@ class YangUtilsSpec extends Specification{ given: 'a yang model (file)' def file = new File(ClassLoader.getSystemClassLoader().getResource('bookstore.yang').getFile()) when: 'the file is parsed' - def result = YangUtils.parseYangModelFile(file) + def result = YangUtils.parseYangModelFiles(Collections.singletonList(file)).getSchemaContext() then: 'the result contain 1 module of the correct name and revision' result.modules.size() == 1 def optionalModule = result.findModule('stores', Revision.of('2020-09-15')) @@ -45,7 +45,7 @@ class YangUtilsSpec extends Specification{ given: 'a file with #description' File file = new File(ClassLoader.getSystemClassLoader().getResource(filename).getFile()) when: 'the file is parsed' - YangUtils.parseYangModelFile(file) + YangUtils.parseYangModelFiles(Collections.singletonList(file)) then: 'an exception is thrown' thrown(expectedException) where: 'the following parameters are used' @@ -59,7 +59,7 @@ class YangUtilsSpec extends Specification{ def jsonData = org.onap.cps.TestUtils.getResourceFileContent('bookstore.json') and: 'a model for that data' def file = new File(ClassLoader.getSystemClassLoader().getResource('bookstore.yang').getFile()) - def schemaContext = YangUtils.parseYangModelFile(file) + def schemaContext = YangUtils.parseYangModelFiles(Collections.singletonList(file)).getSchemaContext() when: 'the json data is parsed' NormalizedNode result = YangUtils.parseJsonData(jsonData, schemaContext) then: 'the result is a normalized node of the correct type' @@ -70,7 +70,7 @@ class YangUtilsSpec extends Specification{ def 'Parsing invalid data: #description.'() { given: 'a yang model (file)' def file = new File(ClassLoader.getSystemClassLoader().getResource('bookstore.yang').getFile()) - def schemaContext = YangUtils.parseYangModelFile(file) + def schemaContext = YangUtils.parseYangModelFiles(Collections.singletonList(file)).getSchemaContext() when: 'invalid data is parsed' YangUtils.parseJsonData(invalidJson, schemaContext) then: 'an exception is thrown' @@ -84,7 +84,7 @@ class YangUtilsSpec extends Specification{ def 'Breaking a Json Data Object into fragments.'() { given: 'a Yang module' def file = new File(ClassLoader.getSystemClassLoader().getResource('bookstore.yang').getFile()) - def schemaContext = YangUtils.parseYangModelFile(file) + def schemaContext = YangUtils.parseYangModelFiles(Collections.singletonList(file)).getSchemaContext() def module = schemaContext.findModule('stores', Revision.of('2020-09-15')).get() and: 'a normalized node for that model' def jsonData = TestUtils.getResourceFileContent('bookstore.json')