Add XML validation

Issue-ID: DCAEGEN2-1217
Change-Id: Ic76885ab1c8e020a2bded05a264092684a747461
Signed-off-by: JoeOLeary <joseph.o.leary@est.tech>
diff --git a/src/main/java/org/onap/dcaegen2/services/pmmapper/App.java b/src/main/java/org/onap/dcaegen2/services/pmmapper/App.java
index 67d4875..72d1509 100644
--- a/src/main/java/org/onap/dcaegen2/services/pmmapper/App.java
+++ b/src/main/java/org/onap/dcaegen2/services/pmmapper/App.java
@@ -34,6 +34,7 @@
 import org.onap.dcaegen2.services.pmmapper.mapping.Mapper;
 import org.onap.dcaegen2.services.pmmapper.model.MapperConfig;
 import org.onap.dcaegen2.services.pmmapper.healthcheck.HealthCheckHandler;
+import org.onap.dcaegen2.services.pmmapper.utils.XMLValidator;
 import org.onap.logging.ref.slf4j.ONAPLogAdapter;
 import org.slf4j.LoggerFactory;
 import org.slf4j.MDC;
@@ -44,16 +45,22 @@
 public class App {
     private static final ONAPLogAdapter logger = new ONAPLogAdapter(LoggerFactory.getLogger(App.class));
     private static Path mappingTemplate = Paths.get("/opt/app/pm-mapper/etc/mapping.ftl");
+    private static Path xmlSchema = Paths.get("/opt/app/pm-mapper/etc/measCollec_plusString.xsd");
 
     public static void main(String[] args) throws InterruptedException, TooManyTriesException, CBSConfigException, EnvironmentConfigException, CBSServerError, MapperConfigException {
         HealthCheckHandler healthCheckHandler = new HealthCheckHandler();
         Mapper mapper = new Mapper(mappingTemplate);
+        XMLValidator validator = new XMLValidator(xmlSchema);
         DataRouterSubscriber dataRouterSubscriber = new DataRouterSubscriber(event -> {
             event.getHttpServerExchange().unDispatch();
             event.getHttpServerExchange().getResponseSender().send(StatusCodes.OK_STRING);
             MDC.setContextMap(event.getMdc());
-            String ves = mapper.map(event);
-            logger.unwrap().info("Mapped Event: {}", ves);
+            if(!validator.validate(event)){
+                logger.unwrap().info("Event failed validation against schema.");
+            } else {
+                String ves = mapper.map(event);
+                logger.unwrap().info("Mapped Event: {}", ves);
+            }
         });
         MapperConfig mapperConfig = new ConfigHandler().getMapperConfig();
         dataRouterSubscriber.start(mapperConfig);
diff --git a/src/main/java/org/onap/dcaegen2/services/pmmapper/utils/XMLValidator.java b/src/main/java/org/onap/dcaegen2/services/pmmapper/utils/XMLValidator.java
new file mode 100644
index 0000000..a00f80b
--- /dev/null
+++ b/src/main/java/org/onap/dcaegen2/services/pmmapper/utils/XMLValidator.java
@@ -0,0 +1,63 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2019 Nordix Foundation.
+ * ================================================================================
+ * 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.dcaegen2.services.pmmapper.utils;
+
+import lombok.NonNull;
+import org.onap.dcaegen2.services.pmmapper.mapping.Mapper;
+import org.onap.dcaegen2.services.pmmapper.model.Event;
+import org.onap.logging.ref.slf4j.ONAPLogAdapter;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.SAXException;
+
+import javax.xml.XMLConstants;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.file.Path;
+
+public class XMLValidator {
+    private static final ONAPLogAdapter logger = new ONAPLogAdapter(LoggerFactory.getLogger(Mapper.class));
+    private Schema schema;
+    public XMLValidator(Path xmlSchemaDefinition) {
+        SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+        try {
+            schema = schemaFactory.newSchema(xmlSchemaDefinition.toFile());
+        } catch (SAXException exception) {
+            logger.unwrap().error("Failed to read schema", exception);
+            throw new IllegalArgumentException("Bad Schema", exception);
+        }
+    }
+
+    public boolean validate(@NonNull Event event) {
+        try {
+            Validator validator =  schema.newValidator();
+            validator.validate(new StreamSource(new StringReader(event.getBody())));
+            logger.unwrap().info("XML validation successful {}", event);
+            return true;
+        } catch (SAXException | IOException exception) {
+            logger.unwrap().info("XML validation failed {}", event, exception);
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/resources/Dockerfile b/src/main/resources/Dockerfile
index 9291567..9ceba29 100644
--- a/src/main/resources/Dockerfile
+++ b/src/main/resources/Dockerfile
@@ -23,4 +23,4 @@
 WORKDIR /opt/app/pm-mapper
 ADD target/${JAR} /opt/app/pm-mapper/pm-mapper.jar
 ADD target/classes/mapping.ftl /opt/app/pm-mapper/etc/mapping.ftl
-
+ADD target/classes/measCollec_plusString.xsd /opt/app/pm-mapper/etc/measCollec_plusString.xsd
diff --git a/src/main/resources/measCollec_plusString.xsd b/src/main/resources/measCollec_plusString.xsd
new file mode 100644
index 0000000..05edf51
--- /dev/null
+++ b/src/main/resources/measCollec_plusString.xsd
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Modified PM XML file format definition to allow arbitrary string values.
+  Based on 3GPP TS 32.435 Performance Measurement XML file format definition
+  data file XML schema
+  measCollec.xsd
+-->
+
+<schema
+        targetNamespace=
+                "http://www.3gpp.org/ftp/specs/archive/32_series/32.435#measCollec"
+        elementFormDefault="qualified"
+        xmlns="http://www.w3.org/2001/XMLSchema"
+        xmlns:mc=
+                "http://www.3gpp.org/ftp/specs/archive/32_series/32.435#measCollec"
+>
+
+    <!-- Measurement collection data file root XML element -->
+
+    <element name="measCollecFile">
+        <complexType>
+            <sequence>
+                <element name="fileHeader">
+                    <complexType>
+                        <sequence>
+                            <element name="fileSender">
+                                <complexType>
+                                    <attribute name="localDn" type="string" use="optional"/>
+                                    <attribute name="elementType" type="string" use="optional"/>
+                                </complexType>
+                            </element>
+                            <element name="measCollec">
+                                <complexType>
+                                    <attribute name="beginTime" type="dateTime" use="required"/>
+                                </complexType>
+                            </element>
+                        </sequence>
+                        <attribute name="fileFormatVersion" type="string" use="required"/>
+                        <attribute name="vendorName" type="string" use="optional"/>
+                        <attribute name="dnPrefix" type="string" use="optional"/>
+                    </complexType>
+                </element>
+                <element name="measData" minOccurs="0" maxOccurs="unbounded">
+                    <complexType>
+                        <sequence>
+                            <element name="managedElement">
+                                <complexType>
+                                    <attribute name="localDn" type="string" use="optional"/>
+                                    <attribute name="userLabel" type="string" use="optional"/>
+                                    <attribute name="swVersion" type="string" use="optional"/>
+                                </complexType>
+                            </element>
+                            <element name="measInfo" minOccurs="0" maxOccurs="unbounded">
+                                <complexType>
+                                    <sequence>
+                                        <element name="job" minOccurs="0">
+                                            <complexType>
+                                                <attribute name="jobId" type="string" use="required"/>
+                                            </complexType>
+                                        </element>
+                                        <element name="granPeriod">
+                                            <complexType>
+                                                <attribute
+                                                        name="duration"
+                                                        type="duration"
+                                                        use="required"
+                                                />
+                                                <attribute
+                                                        name="endTime"
+                                                        type="dateTime"
+                                                        use="required"
+                                                />
+                                            </complexType>
+                                        </element>
+                                        <element name="repPeriod" minOccurs="0">
+                                            <complexType>
+                                                <attribute name="duration"
+                                                           type="duration" use="required"/>
+                                            </complexType>
+                                        </element>
+                                        <choice>
+                                            <element name="measTypes">
+                                                <simpleType>
+                                                    <list itemType="Name"/>
+                                                </simpleType>
+                                            </element>
+                                            <element name="measType"
+                                                     minOccurs="0" maxOccurs="unbounded">
+                                                <complexType>
+                                                    <simpleContent>
+                                                        <extension base="Name">
+                                                            <attribute name="p"
+                                                                       type="positiveInteger" use="required"/>
+                                                        </extension>
+                                                    </simpleContent>
+                                                </complexType>
+                                            </element>
+                                        </choice>
+                                        <element name="measValue"
+                                                 minOccurs="0" maxOccurs="unbounded">
+                                            <complexType>
+                                                <sequence>
+                                                    <choice>
+                                                        <element name="measResults">
+                                                            <simpleType>
+                                                                <list itemType="mc:measResultType"/>
+                                                            </simpleType>
+                                                        </element>
+                                                        <element name="r"
+                                                                 minOccurs="0" maxOccurs="unbounded">
+                                                            <complexType>
+                                                                <simpleContent>
+                                                                    <extension base="mc:measResultType">
+                                                                        <attribute name="p" type="positiveInteger"
+                                                                                   use="required"/>
+                                                                    </extension>
+                                                                </simpleContent>
+                                                            </complexType>
+                                                        </element>
+                                                    </choice>
+                                                    <element name="suspect" type="boolean" minOccurs="0"/>
+                                                </sequence>
+                                                <attribute name="measObjLdn"
+                                                           type="string" use="required"/>
+                                            </complexType>
+                                        </element>
+                                    </sequence>
+                                    <attribute name="measInfoId" type="string" use="optional"/>
+                                </complexType>
+                            </element>
+                        </sequence>
+                    </complexType>
+                </element>
+                <element name="fileFooter">
+                    <complexType>
+                        <sequence>
+                            <element name="measCollec">
+                                <complexType>
+                                    <attribute name="endTime" type="dateTime" use="required"/>
+                                </complexType>
+                            </element>
+                        </sequence>
+                    </complexType>
+                </element>
+            </sequence>
+        </complexType>
+    </element>
+
+    <!--
+      Removed the 'NIL' restriction.
+    -->
+
+    <simpleType name="measResultType">
+        <union memberTypes="float string">
+        </union>
+    </simpleType>
+
+</schema>
diff --git a/src/test/java/org/onap/dcaegen2/services/pmmapper/utils/XMLValidatorTest.java b/src/test/java/org/onap/dcaegen2/services/pmmapper/utils/XMLValidatorTest.java
new file mode 100644
index 0000000..aca0fe6
--- /dev/null
+++ b/src/test/java/org/onap/dcaegen2/services/pmmapper/utils/XMLValidatorTest.java
@@ -0,0 +1,87 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2019 Nordix Foundation.
+ * ================================================================================
+ * 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.dcaegen2.services.pmmapper.utils;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.onap.dcaegen2.services.pmmapper.model.Event;
+import utils.EventUtils;
+
+
+@ExtendWith(MockitoExtension.class)
+class XMLValidatorTest {
+    private static final Path metadata = Paths.get("src/test/resources/valid_metadata.json");
+    private static final Path dataDirectory = Paths.get("src/test/resources/xml_validator_test/test_data/");
+    private static final Path xsd = Paths.get("src/main/resources/measCollec_plusString.xsd");
+    private XMLValidator objUnderTest;
+
+    @BeforeEach
+    void setup() {
+        objUnderTest = new XMLValidator(xsd);
+    }
+
+    @Test
+    void testBadSchema() {
+        assertThrows(IllegalArgumentException.class, () ->
+                new XMLValidator(Paths.get("src/test/resources/mapping_data/valid_data/meas_type_and_r.xml")));
+    }
+
+    @Test
+    void testNullInput() {
+        assertThrows(NullPointerException.class, () -> objUnderTest.validate(null));
+    }
+
+    @ParameterizedTest
+    @MethodSource("getValidEvents")
+    void testValidEventsPass(Event testEvent) {
+        assertTrue(objUnderTest.validate(testEvent));
+    }
+
+    @ParameterizedTest
+    @MethodSource("getInvalidEvents")
+    void testInvalidEventsFail(Event testEvent) {
+        assertFalse(objUnderTest.validate(testEvent));
+    }
+
+    private static List<Event> getValidEvents() throws IOException {
+        Path validDataDirectory = Paths.get(dataDirectory.toString() + "/valid/");
+        return EventUtils.eventsFromDirectory(validDataDirectory, metadata);
+    }
+
+    private static List<Event> getInvalidEvents() throws IOException {
+        Path invalidDataDirectory = Paths.get(dataDirectory.toString() + "/invalid/");
+        return EventUtils.eventsFromDirectory(invalidDataDirectory, metadata);
+    }
+
+}
diff --git a/src/test/java/utils/EventUtils.java b/src/test/java/utils/EventUtils.java
index b265f1d..90317c2 100644
--- a/src/test/java/utils/EventUtils.java
+++ b/src/test/java/utils/EventUtils.java
@@ -31,19 +31,17 @@
 import java.nio.file.Path;
 import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import org.onap.dcaegen2.services.pmmapper.model.Event;
 import org.onap.dcaegen2.services.pmmapper.model.EventMetadata;
-import org.slf4j.MDC;
 
 public class EventUtils {
 
 
     /**
-     * reads contents of files inside the eventBodyDirectory, combines contents with metadata to make an Event Object.
+     * Reads contents of files inside the eventBodyDirectory, combines contents with metadata to make an Event Object.
      * Fails test in the event of failure to read a file.
      * @param eventBodyDirectory Path to directory with event body files.
      * @param metadataPath Path to file with metadata object.
diff --git a/src/test/resources/xml_validator_test/test_data/invalid/no_file_header.xml b/src/test/resources/xml_validator_test/test_data/invalid/no_file_header.xml
new file mode 100644
index 0000000..8e5669f
--- /dev/null
+++ b/src/test/resources/xml_validator_test/test_data/invalid/no_file_header.xml
@@ -0,0 +1,22 @@
+<measCollecFile xmlns="http://www.3gpp.org/ftp/specs/archive/32_series/32.435#measCollec">
+    <measData>
+        <managedElement swVersion="r0.1" localDn="Dublin"/>
+        <measInfo measInfoId="some meas info id">
+            <job jobId="some jobId"/>
+            <granPeriod endTime="2018-10-02T12:15:00Z" duration="PT900S"/>
+            <repPeriod duration="PT900S"/>
+            <measType p="1">a</measType>
+            <measType p="2">b</measType>
+            <measType p="3">c</measType>
+            <measValue measObjLdn="some measObjLdn">
+                <r p="1">86</r>
+                <r p="2">67</r>
+                <r p="3">14</r>
+                <suspect>false</suspect>
+            </measValue>
+        </measInfo>
+    </measData>
+    <fileFooter>
+        <measCollec endTime="2018-10-02T12:15:00+01:00"/>
+    </fileFooter>
+</measCollecFile>
diff --git a/src/test/resources/xml_validator_test/test_data/valid/no_measdata.xml b/src/test/resources/xml_validator_test/test_data/valid/no_measdata.xml
new file mode 100644
index 0000000..5b8eb5a
--- /dev/null
+++ b/src/test/resources/xml_validator_test/test_data/valid/no_measdata.xml
@@ -0,0 +1,10 @@
+<measCollecFile xmlns="http://www.3gpp.org/ftp/specs/archive/32_series/32.435#measCollec">
+    <fileHeader dnPrefix="some dnPrefix" vendorName="FooBar Ltd"
+                fileFormatVersion="32.435 V10.0">
+        <fileSender localDn="Dublin"/>
+        <measCollec beginTime="2018-10-02T12:00:00+01:00"/>
+    </fileHeader>
+    <fileFooter>
+        <measCollec endTime="2018-10-02T12:15:00+01:00"/>
+    </fileFooter>
+</measCollecFile>