teiv, pgsql-schema-generator: Update graph generation

update entity graph to contain the yang data types

update the relationship graph to contain:
  - directional arrows
  - cardinality

Added generators for puml graph

Issue-ID: SMO-156
Change-Id: Ic16d06b8059c780a6e7a06ff2120399d0070f1dd
Signed-off-by: JvD_Ericsson <jeff.van.dam@est.tech>
diff --git a/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/Attribute.java b/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/Attribute.java
index 72ce40b..a079f31 100644
--- a/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/Attribute.java
+++ b/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/Attribute.java
@@ -32,6 +32,7 @@
 public class Attribute {
     private String name;
     private String dataType;
+    private String yangDataType;
     @Builder.Default
     private Collection<Object> constraints = List.of();
     private String defaultValue;
diff --git a/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/Processor.java b/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/Processor.java
index 4d24eee..4112fa7 100644
--- a/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/Processor.java
+++ b/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/Processor.java
@@ -28,6 +28,8 @@
 import java.util.List;
 
 import org.oran.smo.teiv.pgsqlgenerator.grapghgenerator.EntityGraphGenerator;
+import org.oran.smo.teiv.pgsqlgenerator.grapghgenerator.EntityGraphGeneratorUml;
+import org.oran.smo.teiv.pgsqlgenerator.grapghgenerator.RelationshipGraphGeneratorUml;
 import org.oran.smo.teiv.pgsqlgenerator.schema.consumerdata.ConsumerDataSchemaGenerator;
 import org.oran.smo.teiv.pgsqlgenerator.schema.data.DataSchemaGenerator;
 import org.oran.smo.teiv.pgsqlgenerator.schema.model.ModelSchemaGenerator;
@@ -48,7 +50,9 @@
     private final ModelSchemaGenerator modelSchemaGenerator;
     private final ConsumerDataSchemaGenerator consumerDataSchemaGenerator;
     private final RelationshipGraphGenerator relationshipGraphGenerator;
+    private final RelationshipGraphGeneratorUml relationshipGraphGeneratorUml;
     private final EntityGraphGenerator entityGraphGenerator;
+    private final EntityGraphGeneratorUml entityGraphGeneratorUml;
     @Value("${yang-model.source}")
     private String yangModelDirectory;
 
@@ -69,7 +73,9 @@
         List<Relationship> relationshipsFromModelService = yangModelProcessor.getRelationshipsFromYang(pathToImplementing);
 
         relationshipGraphGenerator.generate(relationshipsFromModelService, entitiesFromModelService);
+        relationshipGraphGeneratorUml.generate(relationshipsFromModelService, entitiesFromModelService);
         entityGraphGenerator.generate(entitiesFromModelService);
+        entityGraphGeneratorUml.generate(entitiesFromModelService);
         dataSchemaGenerator.generate(moduleReferences, entitiesFromModelService, relationshipsFromModelService);
         modelSchemaGenerator.generate(moduleReferences, entitiesFromModelService, relationshipsFromModelService);
         consumerDataSchemaGenerator.generate(moduleReferences, entitiesFromModelService, relationshipsFromModelService);
diff --git a/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/YangModelProcessor.java b/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/YangModelProcessor.java
index ff4a9cf..6d4e790 100644
--- a/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/YangModelProcessor.java
+++ b/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/YangModelProcessor.java
@@ -135,7 +135,8 @@
                 List constraint = List.of(PrimaryKeyConstraint.builder().constraintName("PK_" + yList.getListName() + "_id")
                         .tableName(yList.getListName()).columnToAddConstraintTo("id").build());
 
-                attributes.add(Attribute.builder().name("id").dataType(TEXT).constraints(constraint).build());
+                attributes.add(Attribute.builder().name("id").yangDataType("string").dataType(TEXT).constraints(constraint)
+                        .build());
                 yList.getContainers().forEach(yContainer -> {
                     System.out.printf("\t\tContainer Name: %s \n", yContainer.getContainerName());
                     if (yContainer.getContainerName().equals("attributes")) {
@@ -149,12 +150,13 @@
 
                             if (yLeaf.getDefault() != null) {
 
-                                attributes.add(Attribute.builder().name(yLeaf.getLeafName()).dataType(dataTypeMapping.get(
-                                        yLeaf.getType().getDataType())).defaultValue(yLeaf.getDefault().getValue())
-                                        .constraints(new ArrayList()).build());
+                                attributes.add(Attribute.builder().name(yLeaf.getLeafName()).yangDataType(yLeaf.getType()
+                                        .getDataType()).dataType(dataTypeMapping.get(yLeaf.getType().getDataType()))
+                                        .defaultValue(yLeaf.getDefault().getValue()).constraints(new ArrayList()).build());
                             } else {
-                                attributes.add(Attribute.builder().name(yLeaf.getLeafName()).dataType(dataTypeMapping.get(
-                                        yLeaf.getType().getDataType())).constraints(new ArrayList()).build());
+                                attributes.add(Attribute.builder().name(yLeaf.getLeafName()).yangDataType(yLeaf.getType()
+                                        .getDataType()).dataType(dataTypeMapping.get(yLeaf.getType().getDataType()))
+                                        .constraints(new ArrayList()).build());
                             }
                         });
                         yContainer.getLeafLists().forEach(yLeafList -> {
@@ -164,8 +166,9 @@
                             System.out.printf("\t\t\t\tData Type: %s \n", dataTypeMapping.get(yLeafList.getType()
                                     .getDataType()));
 
-                            attributes.add(Attribute.builder().name(yLeafList.getLeafListName()).dataType(JSONB).indexType(
-                                    IndexType.GIN_TRGM_OPS_ON_LIST_AS_JSONB).constraints(new ArrayList()).build());
+                            attributes.add(Attribute.builder().name(yLeafList.getLeafListName()).yangDataType(yLeafList
+                                    .getType().getDataType()).dataType(JSONB).indexType(
+                                            IndexType.GIN_TRGM_OPS_ON_LIST_AS_JSONB).constraints(new ArrayList()).build());
                         });
                         yContainer.getContainers().forEach(container -> {
 
@@ -176,7 +179,8 @@
 
                             String dataType = dataTypeMapping.get(container.getUses().toString());
                             Attribute.AttributeBuilder attributeBuilder = Attribute.builder().name(container
-                                    .getContainerName()).dataType(dataType).constraints(new ArrayList());
+                                    .getContainerName()).yangDataType(dataType).dataType(dataType).constraints(
+                                            new ArrayList());
                             if (container.getContainerName().equals("geo-location")) {
                                 dataType = dataTypeMapping.get("geo:geo-location");
                             }
@@ -191,8 +195,8 @@
 
                             attributes.add(Attribute.builder().name(uses.getDomElement().getValue().substring(uses
                                     .getDomElement().getValue().indexOf(':') + 1, uses.getDomElement().getValue().length()))
-                                    .dataType(dataTypeMapping.get(uses.getDomElement().getValue())).constraints(
-                                            new ArrayList()).build());
+                                    .yangDataType(uses.getDomElement().getValue()).dataType(dataTypeMapping.get(uses
+                                            .getDomElement().getValue())).constraints(new ArrayList()).build());
                         });
                     }
                 });
diff --git a/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/grapghgenerator/EntityGraphGenerator.java b/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/grapghgenerator/EntityGraphGenerator.java
index 78ea033..6c564d6 100644
--- a/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/grapghgenerator/EntityGraphGenerator.java
+++ b/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/grapghgenerator/EntityGraphGenerator.java
@@ -90,7 +90,8 @@
         String label = "<TABLE border='1' cellborder='0' cellspacing='0' cellpadding='4'>";
         for (Attribute attribute : attributes) {
             label = label.concat("<TR> <TD bgcolor='#EEEEEE' align='left'>" + attribute
-                    .getName() + "</TD> <TD align='right' bgcolor='#EEEEEE'>" + attribute.getDataType() + "</TD> </TR>");
+                    .getName() + "</TD> <TD align='right' bgcolor='#EEEEEE'>" + attribute
+                            .getYangDataType() + "</TD> </TR>");
         }
         label = label.concat("</TABLE>");
         MutableNode attributeNode = Factory.mutNode(moduleEntity.getEntityName() + "-attributes").attrs().add(Label.html(
diff --git a/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/grapghgenerator/EntityGraphGeneratorUml.java b/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/grapghgenerator/EntityGraphGeneratorUml.java
new file mode 100644
index 0000000..b15c99f
--- /dev/null
+++ b/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/grapghgenerator/EntityGraphGeneratorUml.java
@@ -0,0 +1,86 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 Ericsson
+ *  Modifications Copyright (C) 2024 OpenInfra Foundation Europe
+ *  ================================================================================
+ *  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.oran.smo.teiv.pgsqlgenerator.grapghgenerator;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.List;
+
+import org.oran.smo.teiv.pgsqlgenerator.Attribute;
+import org.oran.smo.teiv.pgsqlgenerator.Entity;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import lombok.extern.slf4j.Slf4j;
+
+@Component
+@Slf4j
+public class EntityGraphGeneratorUml {
+
+    @Value("${graphs.generate}")
+    private boolean generateEntityGraph;
+
+    @Value("${graphs.output}")
+    private String graphOutput;
+
+    public void generate(List<Entity> entities) throws IOException {
+        if (generateEntityGraph) {
+            List<String> moduleNames = entities.stream().map(Entity::getModuleReferenceName).distinct().toList();
+            for (String moduleName : moduleNames) {
+                List<Entity> moduleEntities = entities.stream().filter(entity -> moduleName.equals(entity
+                        .getModuleReferenceName())).toList();
+                generateGraph(moduleName, moduleEntities);
+            }
+        } else {
+            log.info("graphs.generate set to false");
+        }
+    }
+
+    private void generateGraph(String name, List<Entity> entities) throws IOException {
+        String plantUmlSource = prepareGraphSource(name, entities);
+        File outputFile = new File(graphOutput, name + ".puml");
+        try (PrintWriter writer = new PrintWriter(outputFile)) {
+            writer.write(plantUmlSource);
+        }
+        log.info("PUML generated at: {}", outputFile.getAbsolutePath());
+    }
+
+    private String prepareGraphSource(String moduleName, List<Entity> entities) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("@startuml\n");
+        sb.append("skinparam class {\n");
+        sb.append("    BackgroundColor<<Entity>> LightGray\n");
+        sb.append("    BackgroundColor<<Module>> LightBlue\n");
+        sb.append("}\n");
+        sb.append(String.format("class %s <<Module>> {\n}\n", moduleName));
+        for (Entity entity : entities) {
+            sb.append(String.format("class %s <<Entity>> {\n", entity.getEntityName()));
+            List<Attribute> attributes = entity.getAttributes();
+            for (Attribute attribute : attributes) {
+                sb.append(String.format("    %s : %s\n", attribute.getName(), attribute.getYangDataType()));
+            }
+            sb.append("}\n");
+            sb.append(String.format("\"%s\" --> %s\n", moduleName, entity.getEntityName()));
+        }
+        sb.append("@enduml\n");
+        return sb.toString();
+    }
+}
diff --git a/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/grapghgenerator/RelationshipGraphGenerator.java b/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/grapghgenerator/RelationshipGraphGenerator.java
index bec8e3b..98acbdf 100644
--- a/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/grapghgenerator/RelationshipGraphGenerator.java
+++ b/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/grapghgenerator/RelationshipGraphGenerator.java
@@ -20,6 +20,10 @@
  */
 package org.oran.smo.teiv.pgsqlgenerator.grapghgenerator;
 
+import guru.nidi.graphviz.attribute.Arrow;
+import guru.nidi.graphviz.attribute.Color;
+import guru.nidi.graphviz.attribute.EndLabel;
+import guru.nidi.graphviz.attribute.Shape;
 import guru.nidi.graphviz.model.Factory;
 import guru.nidi.graphviz.model.MutableGraph;
 import guru.nidi.graphviz.model.MutableNode;
@@ -70,7 +74,8 @@
     }
 
     private MutableGraph prepareGraph(List<Relationship> moduleRelationships, List<Entity> moduleEntities) {
-        MutableGraph g = Factory.mutGraph("moduleName").setDirected(false);
+        MutableGraph g = Factory.mutGraph("moduleName").setDirected(true).linkAttrs().add(Color.DARKSLATEGRAY4).nodeAttrs()
+                .add(Shape.BOX);
         for (Entity moduleEntity : moduleEntities) {
             MutableNode node = Factory.mutNode(moduleEntity.getEntityName());
             g.add(node);
@@ -80,9 +85,28 @@
             g.add(nodeA);
             MutableNode nodeB = Factory.mutNode(moduleRelationship.getBSideMOType());
             g.add(nodeB);
+
             String label = moduleRelationship.getName().split("_")[1];
-            g.add(nodeA.addLink(Factory.to(nodeB).with(Label.of(label))));
+            Label aSideCardinality = Label.of(getCardinality(moduleRelationship.getASideMinCardinality(), moduleRelationship
+                    .getASideMaxCardinality()));
+            Label bSideCardinality = Label.of(getCardinality(moduleRelationship.getBSideMinCardinality(), moduleRelationship
+                    .getBSideMaxCardinality()));
+
+            g.add(nodeA.addLink(Factory.to(nodeB).with(Label.of(label), EndLabel.head(aSideCardinality, null, null),
+                    EndLabel.tail(bSideCardinality, null, null), Arrow.VEE)));
         }
         return g;
     }
+
+    private String getCardinality(long minCardinality, long maxCardinality) {
+        return formatCardinality(minCardinality) + ".." + formatCardinality(maxCardinality);
+    }
+
+    private String formatCardinality(long cardinality) {
+        if (cardinality == Long.MAX_VALUE) {
+            return "*";
+        } else {
+            return String.valueOf(cardinality);
+        }
+    }
 }
diff --git a/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/grapghgenerator/RelationshipGraphGeneratorUml.java b/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/grapghgenerator/RelationshipGraphGeneratorUml.java
new file mode 100644
index 0000000..f5a4211
--- /dev/null
+++ b/pgsql-schema-generator/src/main/java/org/oran/smo/teiv/pgsqlgenerator/grapghgenerator/RelationshipGraphGeneratorUml.java
@@ -0,0 +1,99 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 Ericsson
+ *  Modifications Copyright (C) 2024 OpenInfra Foundation Europe
+ *  ================================================================================
+ *  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.oran.smo.teiv.pgsqlgenerator.grapghgenerator;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.List;
+
+import org.oran.smo.teiv.pgsqlgenerator.Entity;
+import org.oran.smo.teiv.pgsqlgenerator.Relationship;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Component
+@Slf4j
+public class RelationshipGraphGeneratorUml {
+
+    @Value("${graphs.generate}")
+    private boolean generateRelationshipGraph;
+
+    @Value("${graphs.output}")
+    private String graphOutput;
+
+    public void generate(List<Relationship> relationships, List<Entity> entities) throws IOException {
+        if (generateRelationshipGraph) {
+            List<String> moduleNames = relationships.stream().map(Relationship::getModuleReferenceName).distinct().toList();
+            for (String moduleName : moduleNames) {
+                List<Relationship> moduleRelationships = relationships.stream().filter(relationship -> moduleName.equals(
+                        relationship.getModuleReferenceName())).toList();
+                List<Entity> moduleEntities = entities.stream().filter(entity -> moduleName.equals(entity
+                        .getModuleReferenceName())).toList();
+                generateGraph(moduleName, moduleRelationships, moduleEntities);
+            }
+            generateGraph("overall", relationships, entities);
+        } else {
+            log.info("graphs.generate set to false");
+        }
+    }
+
+    private void generateGraph(String name, List<Relationship> relationships, List<Entity> entities) throws IOException {
+        String plantUmlSource = prepareGraph(relationships, entities);
+        File outputFile = new File(graphOutput, name + "-rel.puml");
+        try (PrintWriter writer = new PrintWriter(outputFile)) {
+            writer.write(plantUmlSource);
+        }
+        log.info("PUML generated at: {}", outputFile.getAbsolutePath());
+    }
+
+    private String prepareGraph(List<Relationship> relationships, List<Entity> entities) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("@startuml\n");
+        sb.append("skinparam componentStyle rectangle\n");
+        for (Entity entity : entities) {
+            sb.append(String.format("class %s {\n", entity.getEntityName()));
+            sb.append("}\n");
+        }
+        for (Relationship relationship : relationships) {
+            String label = relationship.getName().split("_")[1];
+            String aSideCardinality = getCardinality(relationship.getASideMinCardinality(), relationship
+                    .getASideMaxCardinality());
+            String bSideCardinality = getCardinality(relationship.getBSideMinCardinality(), relationship
+                    .getBSideMaxCardinality());
+
+            sb.append(String.format("%s \"%s\" --> \"%s\" %s : %s\n", relationship.getASideMOType(), aSideCardinality,
+                    bSideCardinality, relationship.getBSideMOType(), label));
+        }
+        sb.append("@enduml\n");
+        return sb.toString();
+    }
+
+    private String getCardinality(long minCardinality, long maxCardinality) {
+        return formatCardinality(minCardinality) + ".." + formatCardinality(maxCardinality);
+    }
+
+    private String formatCardinality(long cardinality) {
+        return cardinality == Long.MAX_VALUE ? "*" : String.valueOf(cardinality);
+    }
+}