UNOMI-565 : add code coverage report (#411)

* add jacoco report

* improve folder detection

* improve sources resolution

* make it work offline

* update profile name
diff --git a/.github/workflows/unomi-ci-build-tests.yml b/.github/workflows/unomi-ci-build-tests.yml
index cb5f824..bba86b1 100644
--- a/.github/workflows/unomi-ci-build-tests.yml
+++ b/.github/workflows/unomi-ci-build-tests.yml
@@ -14,6 +14,7 @@
     name: Execute unit tests
     runs-on: ubuntu-latest
     strategy:
+      fail-fast: false
       matrix:
         java: [ 1.8, 11]
     steps:
@@ -30,6 +31,7 @@
     name: Execute integration tests
     runs-on: ubuntu-latest
     strategy:
+      fail-fast: false
       matrix:
         java: [ 1.8, 11]
     steps:
@@ -41,11 +43,16 @@
           cache: maven
       - name: Integration tests
         run: mvn -ntp clean install -Pintegration-tests
+      - name: Archive code coverage logs
+        uses: actions/upload-artifact@v3
+        with:
+          name: unomi-code-coverage-jdk${{ matrix.java }}-${{ github.run_number }}
+          path: itests/target/site/jacoco
       - name: Archive unomi logs
         uses: actions/upload-artifact@v3
         if: failure()
         with:
-          name: unomi-${{ matrix.java }}-${{ github.run_number }}
+          name: unomi-log-jdk${{ matrix.java }}-${{ github.run_number }}
           path: itests/target/exam/**/data/log
       - name: Publish Test Report
         uses: mikepenz/action-junit-report@v3
diff --git a/.gitignore b/.gitignore
index 3aef7a6..1d2e17a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,4 +14,5 @@
 allCountries.zip
 rest/.miredot-offline.json
 /extensions/salesforce-connector/test.properties
-**/*.versionsBackup
\ No newline at end of file
+**/*.versionsBackup
+itests/src/main
\ No newline at end of file
diff --git a/itests/jacoco-report.sh b/itests/jacoco-report.sh
new file mode 100755
index 0000000..b4ff826
--- /dev/null
+++ b/itests/jacoco-report.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+################################################################################
+#
+#    Licensed to the Apache Software Foundation (ASF) under one or more
+#    contributor license agreements.  See the NOTICE file distributed with
+#    this work for additional information regarding copyright ownership.
+#    The ASF licenses this file to You 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.
+#
+################################################################################
+echo "Copy sources and classes locally"
+for project in $(echo ../*/); do
+  echo "  get sources for $project"
+  if [[ -d ${project}target ]]; then
+    echo "    sources and target found for $project"
+    cp -rf ${project}src/main src
+    cp -rf ${project}target/classes target
+    for subproject in $(echo ${project}*/); do
+      echo "      get sources for sub $subproject"
+      if [[ -d ${subproject}target/classes ]]; then
+        echo "        sources and target found for $subproject"
+        cp -rf ${subproject}src/main src
+        cp -rf ${subproject}target/classes target
+      fi
+      for subsubproject in $(echo ${subproject}*/); do
+        echo "      get sources for sub $subsubproject"
+        if [[ -d ${subsubproject}target/classes ]]; then
+          echo "        sources and target found for $subsubproject"
+          cp -rf ${subsubproject}src/main src
+          cp -rf ${subsubproject}target/classes target
+        fi
+      done
+    done
+  fi
+done
+mvn jacoco:report -Dit.code.coverage=true
+echo "clean up src/main"
+rm -rf src/main
diff --git a/itests/pom.xml b/itests/pom.xml
index 86955ad..1de0f63 100644
--- a/itests/pom.xml
+++ b/itests/pom.xml
@@ -16,7 +16,8 @@
   ~ limitations under the License.
   -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.apache.unomi</groupId>
@@ -151,6 +152,7 @@
     </dependencies>
 
     <profiles>
+
         <profile>
             <id>run-tests</id>
             <activation>
@@ -158,6 +160,29 @@
             </activation>
             <build>
                 <plugins>
+                    <plugin>
+                        <groupId>com.googlecode.maven-download-plugin</groupId>
+                        <artifactId>download-maven-plugin</artifactId>
+                        <version>1.3.0</version>
+                        <executions>
+                            <execution>
+                                <!-- the wget goal actually binds itself to this phase by default -->
+                                <phase>pre-integration-test</phase>
+                                <goals>
+                                    <goal>wget</goal>
+                                </goals>
+                                <configuration>
+                                    <url>
+                                        https://search.maven.org/remotecontent?filepath=org/jacoco/jacoco/0.8.8/jacoco-0.8.8.zip
+                                    </url>
+                                    <outputFileName>jacoco.zip</outputFileName>
+                                    <unpack>true</unpack>
+                                    <outputDirectory>${project.build.directory}/jacoco/</outputDirectory>
+                                    <failOnError>false</failOnError>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
                     <!-- Needed if you use versionAsInProject() -->
                     <plugin>
                         <groupId>org.apache.servicemix.tooling</groupId>
@@ -188,7 +213,8 @@
                             </environmentVariables>
                             <instanceSettings>
                                 <properties>
-                                    <cluster.routing.allocation.disk.threshold_enabled>false</cluster.routing.allocation.disk.threshold_enabled>
+                                    <cluster.routing.allocation.disk.threshold_enabled>false
+                                    </cluster.routing.allocation.disk.threshold_enabled>
                                     <http.cors.allow-origin>*</http.cors.allow-origin>
                                 </properties>
                             </instanceSettings>
@@ -238,6 +264,49 @@
                             </execution>
                         </executions>
                     </plugin>
+                    <plugin>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <executions>
+                            <execution>
+                                <id>Generate code coverage report</id>
+                                <phase>verify</phase>
+                                <goals>
+                                    <goal>exec</goal>
+                                </goals>
+                                <configuration>
+                                    <executable>${basedir}/jacoco-report.sh</executable>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>itest-code-coverage</id>
+            <activation>
+                <property>
+                    <name>it.code.coverage</name>
+                    <value>true</value>
+                </property>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.jacoco</groupId>
+                        <artifactId>jacoco-maven-plugin</artifactId>
+                        <version>0.7.7.201606060606</version>
+                        <executions>
+                            <execution>
+                                <phase>post-site</phase>
+                                <id>report</id>
+                                <goals>
+                                    <goal>report</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
                 </plugins>
             </build>
         </profile>
diff --git a/itests/src/test/java/org/apache/unomi/itests/BaseIT.java b/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
index 1007ff7..9abf854 100644
--- a/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
@@ -73,6 +73,9 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.security.KeyManagementException;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
@@ -125,7 +128,8 @@
     @Inject
     protected BundleContext bundleContext;
 
-    @Inject @Filter(timeout = 600000)
+    @Inject
+    @Filter(timeout = 600000)
     protected BundleWatcher bundleWatcher;
 
     @Inject
@@ -150,7 +154,7 @@
     }
 
 
-    protected void removeItems(final Class<? extends Item> ...classes) throws InterruptedException {
+    protected void removeItems(final Class<? extends Item>... classes) throws InterruptedException {
         Condition condition = new Condition(definitionsService.getConditionType("matchAllCondition"));
         for (Class<? extends Item> aClass : classes) {
             persistenceService.removeByQuery(condition, aClass);
@@ -257,6 +261,17 @@
             options.add(0, debugConfiguration(port, hold));
         }
 
+        // Jacoco setup
+        final String agentFile = System.getProperty("user.dir") + "/target/jacoco/lib/jacocoagent.jar";
+        Path path = Paths.get(agentFile);
+        if (Files.exists(path)) {
+            final String jacocoOption = "-javaagent:" + agentFile + "=destfile=" + System.getProperty("user.dir") + "/target/jacoco.exec,includes=org.apache.unomi.*";
+            LOGGER.info("set jacoco java agent: {}", jacocoOption);
+            options.add(new VMOption(jacocoOption));
+        } else {
+            LOGGER.warn("Unable to set jacoco agent as {} was not found", agentFile);
+        }
+
         if (JavaVersionUtil.getMajorVersion() >= 9) {
             Option[] jdk9PlusOptions = new Option[]{
                     new VMOption("--add-reads=java.xml=java.logging"),
@@ -338,10 +353,10 @@
         }
     }
 
-    protected String getValidatedBundleJSON(final String resourcePath, Map<String,String> parameters) throws IOException {
+    protected String getValidatedBundleJSON(final String resourcePath, Map<String, String> parameters) throws IOException {
         String jsonString = bundleResourceAsString(resourcePath);
         if (parameters != null && parameters.size() > 0) {
-            for (Map.Entry<String,String> parameterEntry : parameters.entrySet()) {
+            for (Map.Entry<String, String> parameterEntry : parameters.entrySet()) {
                 jsonString = jsonString.replace("###" + parameterEntry.getKey() + "###", parameterEntry.getValue());
             }
         }
@@ -378,7 +393,7 @@
         ServiceListener serviceListener = e -> {
             LOGGER.info("Service {} {}", e.getServiceReference().getProperty("objectClass"), serviceEventTypeToString(e));
             if ((e.getType() == ServiceEvent.UNREGISTERING || e.getType() == ServiceEvent.REGISTERED)
-                    && ((String[])e.getServiceReference().getProperty("objectClass"))[0].equals(serviceName)) {
+                    && ((String[]) e.getServiceReference().getProperty("objectClass"))[0].equals(serviceName)) {
                 latch1.countDown();
             }
         };
@@ -469,7 +484,7 @@
     }
 
     protected CloseableHttpResponse post(final String url, final String resource) {
-        return post(url,resource, JSON_CONTENT_TYPE);
+        return post(url, resource, JSON_CONTENT_TYPE);
     }
 
     protected CloseableHttpResponse delete(final String url) {
@@ -588,6 +603,7 @@
     void registerEventType(String jsonSchemaFileName) {
         post(JSONSCHEMA_URL, "schemas/events/" + jsonSchemaFileName, ContentType.TEXT_PLAIN);
     }
+
     void unRegisterEventType(String jsonSchemaId) {
         delete(JSONSCHEMA_URL + "/" + Base64.getEncoder().encodeToString(jsonSchemaId.getBytes()));
     }