Introduce YangTextSchemaSourceSet 34/116334/4
authorClaudio David Gasparini <claudio.gasparini@pantheon.tech>
Mon, 14 Dec 2020 08:49:13 +0000 (09:49 +0100)
committerClaudio David Gasparini <claudio.gasparini@pantheon.tech>
Wed, 16 Dec 2020 09:49:26 +0000 (09:49 +0000)
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 <claudio.gasparini@pantheon.tech>
Change-Id: I9cba490dec25defbddbb3524c6d6c1535bee63bc

cps-service/pom.xml
cps-service/src/main/java/org/onap/cps/utils/YangUtils.java
cps-service/src/main/java/org/onap/cps/yang/YangTextSchemaSourceSet.java [new file with mode: 0644]
cps-service/src/main/java/org/onap/cps/yang/YangTextSchemaSourceSetBuilder.java [new file with mode: 0644]
cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy

index 3e8cc2d..642d764 100644 (file)
   <artifactId>cps-service</artifactId>\r
 \r
   <dependencies>\r
+    <dependency>\r
+      <groupId>org.opendaylight.yangtools</groupId>\r
+      <artifactId>yang-model-api</artifactId>\r
+    </dependency>\r
     <dependency>\r
       <groupId>org.opendaylight.yangtools</groupId>\r
       <artifactId>yang-parser-api</artifactId>\r
index 071ff6a..a35533c 100644 (file)
 
 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<YangParserFactory> 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<File> 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 (file)
index 0000000..5c33777
--- /dev/null
@@ -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 (file)
index 0000000..1588c44
--- /dev/null
@@ -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<String, String> 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<String, String> 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<String, String> 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<String, String> yangResourceNameToContent)
+            throws IOException, ReactorException, YangSyntaxErrorException {
+        final CrossSourceStatementReactor.BuildAction reactor = RFC7950Reactors.defaultReactor().newBuild();
+        final List<YangTextSchemaSource> yangTextSchemaSources = forResources(yangResourceNameToContent);
+        for (final YangTextSchemaSource yangTextSchemaSource : yangTextSchemaSources) {
+            reactor.addSource(YangStatementStreamSource.create(yangTextSchemaSource));
+        }
+        return reactor.buildEffective();
+    }
+
+    private List<YangTextSchemaSource> forResources(final Map<String, String> 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<String, String> 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());
+            }
+        };
+    }
+}
index 801e430..e002b18 100644 (file)
@@ -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')