Merge "Add multi document Yaml file loading"
diff --git a/csarvalidation/src/main/java/org/onap/validation/yaml/YamlLoader.java b/csarvalidation/src/main/java/org/onap/validation/yaml/YamlLoader.java
new file mode 100644
index 0000000..804f1ea
--- /dev/null
+++ b/csarvalidation/src/main/java/org/onap/validation/yaml/YamlLoader.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2020 Nokia
+ *
+ * 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.
+ *
+ */
+
+package org.onap.validation.yaml;
+
+import org.onap.validation.yaml.model.YamlDocument;
+import org.onap.validation.yaml.model.YamlDocumentFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.yaml.snakeyaml.Yaml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+public class YamlLoader {
+
+    private static final Logger LOGGER =  LoggerFactory.getLogger(YamlLoader.class);
+
+    public List<YamlDocument> loadMultiDocumentYamlFile(URL path)
+        throws YamlDocumentFactory.YamlDocumentParsingException {
+        List<YamlDocument> documentsFromFile = new ArrayList<>();
+        try (InputStream yamlStream = path.openStream()) {
+            for (Object yamlDocument : new Yaml().loadAll(yamlStream)) {
+                documentsFromFile.add(
+                    new YamlDocumentFactory().createYamlDocument(yamlDocument)
+                );
+            }
+        } catch (IOException e) {
+            LOGGER.error("Failed to load multi document YAML file",e);
+        }
+        return documentsFromFile;
+    }
+
+}
diff --git a/csarvalidation/src/main/java/org/onap/validation/yaml/model/YamlDocument.java b/csarvalidation/src/main/java/org/onap/validation/yaml/model/YamlDocument.java
new file mode 100644
index 0000000..5ed4690
--- /dev/null
+++ b/csarvalidation/src/main/java/org/onap/validation/yaml/model/YamlDocument.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 Nokia
+ *
+ * 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.
+ *
+ */
+
+package org.onap.validation.yaml.model;
+
+import java.util.Map;
+
+public class YamlDocument {
+
+    private final Map<String, ?> yaml;
+
+    public YamlDocument(Map<String, ?> yaml) {
+        this.yaml = yaml;
+    }
+
+    public Map<String, ?> getYaml() {
+        return yaml;
+    }
+}
+
+
diff --git a/csarvalidation/src/main/java/org/onap/validation/yaml/model/YamlDocumentFactory.java b/csarvalidation/src/main/java/org/onap/validation/yaml/model/YamlDocumentFactory.java
new file mode 100644
index 0000000..8e571a9
--- /dev/null
+++ b/csarvalidation/src/main/java/org/onap/validation/yaml/model/YamlDocumentFactory.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2020 Nokia
+ *
+ * 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.
+ *
+ */
+
+package org.onap.validation.yaml.model;
+
+import java.util.Map;
+
+public class YamlDocumentFactory {
+
+    public YamlDocument createYamlDocument(Object yaml) throws YamlDocumentParsingException {
+        try {
+            Map<String, ?> parsedYaml = (Map<String, ?>) yaml;
+            return new YamlDocument(parsedYaml);
+        } catch (ClassCastException e) {
+            throw new YamlDocumentParsingException(
+                String.format("Fail to parse given objects: %s as yaml",yaml), e
+            );
+        }
+    }
+
+    public static class YamlDocumentParsingException extends Exception {
+
+        YamlDocumentParsingException(String message, Throwable throwable) {
+            super(message, throwable);
+        }
+    }
+
+}
diff --git a/csarvalidation/src/test/java/org/onap/validation/yaml/YamlLoaderTest.java b/csarvalidation/src/test/java/org/onap/validation/yaml/YamlLoaderTest.java
new file mode 100644
index 0000000..853d558
--- /dev/null
+++ b/csarvalidation/src/test/java/org/onap/validation/yaml/YamlLoaderTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2020 Nokia
+ *
+ * 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.
+ *
+ */
+
+package org.onap.validation.yaml;
+
+import org.junit.Test;
+import org.onap.validation.yaml.model.YamlDocument;
+import org.onap.validation.yaml.model.YamlDocumentFactory;
+
+import java.net.URL;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+public class YamlLoaderTest {
+
+    private static final String PATH_TO_VALID_YAML = "yaml_schema/PM_Dictionary.yaml";
+
+
+    @Test
+    public void shouldLoadAllDocumentsFromYamlFile() throws YamlDocumentFactory.YamlDocumentParsingException {
+        // given
+        YamlLoader loader = new YamlLoader();
+        URL urlOfValidYaml = getUrlForGivenPath(PATH_TO_VALID_YAML);
+
+        // when
+        List<YamlDocument> documents = loader.loadMultiDocumentYamlFile(urlOfValidYaml);
+
+        // then
+        assertThat(documents.size()).isEqualTo(4);
+    }
+
+    private URL getUrlForGivenPath(String path) {
+        return this.getClass().getClassLoader().getResource(path);
+    }
+}
diff --git a/csarvalidation/src/test/resources/yaml_schema/PM_Dictionary.yaml b/csarvalidation/src/test/resources/yaml_schema/PM_Dictionary.yaml
new file mode 100644
index 0000000..5384dcd
--- /dev/null
+++ b/csarvalidation/src/test/resources/yaml_schema/PM_Dictionary.yaml
@@ -0,0 +1,209 @@
+---
+# PM Dictionary schema specifying and describing the meta information
+# used to define perf3gpp measurements in the PM Dictionary
+pmMetaData: { presence: required, structure: {
+  pmHeader: {
+    presence: required,
+    structure: {
+      nfType: {
+        presence: required,
+        comment: "NF type; should match the nfName-vendor string used in
+                      the fileReady or perf3gpp eventName"
+      },
+      pmDefSchemaVsn: {
+        presence: required,
+        value: [2.0],
+        comment: "PM Dictionary Schema Version from the VES Event
+                      Registration specification"
+      },
+      pmDefVsn: {
+        presence: required,
+        comment: "vendor-defined PM Dictionary version"
+      }
+    }
+  },
+  pmFields: {
+    presence: required,
+    structure: {
+      iMeasInfoId: {
+        presence: required,
+        comment: "vendor-defined integer measurement group identifier"
+      },
+      iMeasType: {
+        presence: required,
+        comment: "vendor-defined integer identifier for the measType;
+                      must be combined with measInfoId to identify a
+                      specific measurement."
+      },
+      measChangeType: {
+        presence: required,
+        value: [added, modified, deleted],
+        comment: "indicates the type of change that occurred during
+                      measLastChange"
+      },
+      measCollectionMethod: {
+        presence: required,
+        value: [CC, SI, DER, Gauge, Average],
+        comment: "the measurement collection method; CC, SI, DER and
+                      Gauge are as defined in 3GPP; average contains the
+                      average value of the measurement during the
+                      granularity period"
+      },
+      measCondition: {
+        presence: required,
+        comment: "description of the condition causing the measurement"
+      },
+      measDescription: {
+        presence: required,
+        comment: "description of the measurement information
+                      and purpose"
+      },
+      measFamily: {
+        presence: required,
+        comment: "abbreviation for a family of measurements, in
+                      3GPP format, or vendor defined"
+      },
+      measInfoId: {
+        presence: required,
+        comment: "name for a group of related measurements in
+                      3GPP format or vendor defined"
+      },
+      measLastChange: {
+        presence: required,
+        comment: "version of the PM Dictionary the last time this
+                      measurement was added, modified or deleted"
+      },
+      measObjClass: {
+        presence: required,
+        value: [NGBTS, NGCELL, IPNO, IPSEC, ETHIF],
+        comment: "measurement object class"
+      },
+      measResultRange: {
+        presence: optional,
+        comment: "range of the measurement result; only necessary when
+                      the range is smaller than the full range of the
+                      data type"
+      },
+      measResultType: {
+        presence: required,
+        value: [float, uint32, uint64],
+        comment: "data type of the measurement result"
+      },
+      measResultUnits: {
+        presence: required,
+        value: [seconds, minutes, nanoseconds, microseconds, dB,
+                number, kilobytes, bytes, ethernetFrames,
+                packets, users],
+        comment: "units of measure for the measurement result"
+      },
+      measType: {
+        presence: required,
+        comment: "measurement name in 3GPP or vendor-specific format;
+                      vendor specific names are preceded with VS"
+      },
+      measAdditionalFields: {
+        presence: required,
+        comment: "vendor-specific PM Dictionary fields",
+        structure: {
+          vendorField1: {
+            presence: required,
+            value: [X, Y, Z],
+            comment: "vendor field 1 description"
+          },
+          vendorField2: {
+            presence: optional,
+            value: [A, B],
+            comment: "vendor field 2 description."
+          }
+        }
+      }
+    }
+  }
+}}
+...
+# PM Dictionary perf3gpp measurements for the gnb-Nokia NF (bracket style yaml)
+---
+pmMetaData: {
+  pmHeader: {
+    nfType: gnb-Nokia,
+    pmDefSchemaVsn: 2.0,
+    pmDefVsn: 5G19_1906_002
+  },
+  pmFields: {
+    iMeasInfoId: 2204,
+    iMeasType: 1,
+
+    measCollectionMethod: CC,
+    measCondition: "This measurement is updated when X2AP: SgNB Modification Required message is sent to MeNB
+                      with the SCG Change Indication set as PSCellChange.",
+    measDescription: "This counter indicates the number of intra gNB intra frequency PSCell change attempts.",
+    measFamily: NINFC,
+    measInfoId: "NR Intra Frequency PSCell Change",
+    measLastChange: 5G18A_1807_003,
+    measObjClass: NGCELL,
+    measResultRange: 0-4096,
+    measResultType: integer,
+    measResultUnits: number,
+    measType: VS.NINFC.IntraFrPscelChAttempt,
+    measAdditionalFields: {
+      vendorField1: X,
+      vendorField2: B
+    }
+  }
+}
+...
+---
+pmMetaData: {
+  pmHeader: {
+    nfType: gnb-Nokia,
+    pmDefSchemaVsn: 2.0,
+    pmDefVsn: 5G19_1906_002
+  },
+  pmFields: {
+    iMeasInfoId: 2204,
+    iMeasType: 2,
+    measCollectionMethod: CC,
+    measCondition: "This measurement is updated when the TDCoverall timer has elapsed before gNB receives the X2AP: SgNB Modification Confirm message.",
+    measDescription: "This measurement the number of intra gNB intra frequency PSCell change failures due to TDCoverall timer expiry.",
+    measFamily: NINFC,
+    measInfoId: "NR Intra Frequency PSCell Change",
+    measLastChange: 5G18A_1807_003,
+    measObjClass: NGCELL,
+    measResultRange: 0-4096,
+    measResultType: integer,
+    measResultUnits: number,
+    measType: VS.NINFC.IntraFrPscelChFailTdcExp,
+    measAdditionalFields: {
+      vendorField1: Y
+    }
+  }
+}
+...
+---
+pmMetaData: {
+  pmHeader: {
+    nfType: gnb-Nokia,
+    pmDefSchemaVsn: 2.0,
+    pmDefVsn: 5G19_1906_002
+  },
+  pmFields: {
+    iMeasInfoId: 2206,
+    iMeasType: 1,
+    measCondition: "This measurement is updated when MeNB replies to X2AP: SgNB Modification Required message with the X2AP: SgNB Modification Refuse message.",
+    measCollectionMethod: CC,
+    measDescription: "This counter indicates the number of intra gNB intra frequency PSCell change failures due to MeNB refusal.",
+    measFamily: NINFC,
+    measInfoId: "NR Intra Frequency PSCell Change",
+    measLastChange: 5G19_1906_002,
+    measObjClass: NGCELL,
+    measResultRange: 0-4096,
+    measResultType: integer,
+    measResultUnits: number,
+    measType: VS.NINFC.IntraFrPscelChFailMenbRef,
+    measAdditionalFields: {
+      vendorField1: Z,
+      vendorField2: A
+    }
+  }
+}
+...