11package org .ngbsn .generator ;
22
33import org .apache .commons .text .CaseUtils ;
4+ import org .apache .commons .text .WordUtils ;
45import org .ngbsn .model .Column ;
6+ import org .ngbsn .model .EmbeddableClass ;
57import org .ngbsn .model .ForeignKeyConstraint ;
68import org .ngbsn .model .Table ;
79import org .ngbsn .model .annotations .fieldAnnotations .*;
1012import java .util .List ;
1113import java .util .Optional ;
1214import java .util .Set ;
15+ import java .util .stream .Collectors ;
16+ import java .util .stream .Stream ;
1317
1418import static org .ngbsn .generator .ModelGenerator .tablesMap ;
1519
1620/**
17- * Is there more than 1 foreign key? - This is a linked table
18- * Are all fields foreign keys? @ManyToMany and no separate entity needed for linked table
19- * Are there some non-foreign key fields? - @ManyToOne in a separate entity for linked table
20- * <p>
21- * Is there a primary key in the foreign keys list? Mark this as @MapsId
21+ * This class contains logic for generating all the association mappings
2222 */
2323public class AssociationMappingsGenerator {
2424
@@ -35,17 +35,17 @@ public static void generateMappings(final Table table) {
3535 //Case: There are multiple Foreign Keys or multiple Composite Foreign Keys
3636 //Treat this as a Link table
3737 List <String > foreignKeyColumns = foreignKeyConstraintList .stream ().flatMap (foreignKeyConstraint -> foreignKeyConstraint .getColumns ().stream ()).toList ();
38- Optional <Column > optionalKey = table .getColumns ().stream ().filter (column -> !foreignKeyColumns .contains (column .getName ())).findAny ();
38+ Optional <Column > optionalKey = table .getColumns ().stream ().filter (column -> !foreignKeyColumns .contains (column .getColumnName ())).findAny ();
3939 if (optionalKey .isEmpty () && foreignKeyConstraintList .size () == 2 ) {
4040 //Case: All fields are foreign keys. Also, the relation exits between 2 entities only
4141 //Remove this link entity from the tablesMap as separate entity is not needed to track Link Table. Use @ManyToMany on other 2 entities
42- tablesMap .remove (table .getName ());
42+ tablesMap .remove (table .getTableName ());
4343 Table table1 = tablesMap .get (foreignKeyConstraintList .get (0 ).getReferencedTableName ());
4444 Table table2 = tablesMap .get (foreignKeyConstraintList .get (1 ).getReferencedTableName ());
4545
4646 //Adding @ManyToMany and @JoinTable to table1
4747 Column column1 = new Column ();
48- column1 .setFieldName (CaseUtils .toCamelCase (table2 .getName (), false , '_' ));
48+ column1 .setFieldName (CaseUtils .toCamelCase (table2 .getTableName (), false , '_' ));
4949 column1 .setType (table2 .getClassName ());
5050 column1 .getAnnotations ().add (ManyToManyAnnotation .builder ().build ().toString ());
5151 Set <JoinColumnAnnotation > joinColumnAnnotations = new HashSet <>();
@@ -56,12 +56,12 @@ public static void generateMappings(final Table table) {
5656 for (String column : foreignKeyConstraintList .get (1 ).getColumns ()) {
5757 joinInverseColumnAnnotations .add (JoinColumnAnnotation .builder ().name (column ).build ());
5858 }
59- column1 .getAnnotations ().add (JoinTableAnnotation .builder ().tableName (table .getName ()).joinColumns (joinColumnAnnotations ).inverseJoinColumns (joinInverseColumnAnnotations ).build ().toString ());
59+ column1 .getAnnotations ().add (JoinTableAnnotation .builder ().tableName (table .getTableName ()).joinColumns (joinColumnAnnotations ).inverseJoinColumns (joinInverseColumnAnnotations ).build ().toString ());
6060 table1 .getColumns ().add (column1 );
6161
6262 //Adding @ManyToMany(mappedBy) to table2
6363 Column column2 = new Column ();
64- column2 .setFieldName (CaseUtils .toCamelCase (table1 .getName (), false , '_' ));
64+ column2 .setFieldName (CaseUtils .toCamelCase (table1 .getTableName (), false , '_' ));
6565 column2 .setType (table1 .getClassName ());
6666 column2 .getAnnotations ().add (ManyToManyAnnotation .builder ().mappedBy (column1 .getFieldName ()).build ().toString ());
6767 table2 .getColumns ().add (column2 );
@@ -78,39 +78,107 @@ public static void generateMappings(final Table table) {
7878
7979 }
8080
81+ /**
82+ * This method will handle each foreign key constraint in the table.
83+ *
84+ * @param table table
85+ * @param foreignKeyConstraint foreignKeyConstraint
86+ */
8187 private static void addBothUnidirectionalMappings (Table table , ForeignKeyConstraint foreignKeyConstraint ) {
8288 Table referencedTable = tablesMap .get (foreignKeyConstraint .getReferencedTableName ());
83- Column foreignKeyColumn = new Column ();
84- foreignKeyColumn .setFieldName (CaseUtils .toCamelCase (referencedTable .getName (), false , '_' ));
85- foreignKeyColumn .setType (referencedTable .getClassName ());
86- foreignKeyColumn .getAnnotations ().add (new ManyToOneAnnotation ().toString ());
87- table .getColumns ().add (foreignKeyColumn );
88-
89- Column childKeyColumn = new Column ();
90- childKeyColumn .setFieldName (CaseUtils .toCamelCase (table .getName (), false , '_' ));
91- childKeyColumn .setType ("Set<" + table .getClassName () + ">" );
92- childKeyColumn .getAnnotations ().add (OneToManyAnnotation .builder ().mappedBy (foreignKeyColumn .getFieldName ()).build ().toString ());
93- referencedTable .getColumns ().add (childKeyColumn );
94-
89+ //In the Child table, create a new column having field name as Parent(Referenced) Table.
90+ Column parentTableField = new Column ();
91+ parentTableField .setFieldName (CaseUtils .toCamelCase (referencedTable .getTableName (), false , '_' ));
92+ parentTableField .setType (referencedTable .getClassName ());
93+ parentTableField .getAnnotations ().add (new ManyToOneAnnotation ().toString ());
94+ table .getColumns ().add (parentTableField );
95+
96+ //In the Parent(Referenced) table, create a new column having field name as child Table.
97+ Column childTableField = new Column ();
98+ childTableField .setFieldName (CaseUtils .toCamelCase (table .getTableName (), false , '_' ));
99+ childTableField .setType ("Set<" + table .getClassName () + ">" );
100+ childTableField .getAnnotations ().add (OneToManyAnnotation .builder ().mappedBy (parentTableField .getFieldName ()).build ().toString ());
101+ referencedTable .getColumns ().add (childTableField );
102+
103+ //get EmbeddedId for this table
104+ Optional <EmbeddableClass > optionalEmbeddableId = table .getEmbeddableClasses ().stream ().filter (EmbeddableClass ::isEmbeddedId ).findFirst ();
105+ EmbeddableClass embeddableId = optionalEmbeddableId .orElse (null );
106+ Set <Column > allPrimaryKeyColumns = getAllPrimaryKeys (table , embeddableId ); //get all primary keys
107+
108+ //Case: Composite Foreign key
95109 if (foreignKeyConstraint .getColumns ().size () > 1 ) {
96- //Case: Composite Foreign key
110+ Set <Column > setOfForeignKeyColumns = setOfForeignKeys (table , foreignKeyConstraint );
111+ //If composite foreign key is inside the composite primary key, don't remove them from table.
112+ //This case assumes there is a primary composite key
113+ //Add a @MapsId annotation to the referenced table field
114+ if (embeddableId != null && allPrimaryKeyColumns .containsAll (setOfForeignKeyColumns )) {
115+ EmbeddableClass foreignCompositeKeyEmbedded = new EmbeddableClass (); //Create a new embeddable for this foreign composite key
116+ String embeddableName = setOfForeignKeyColumns .stream ().map (Column ::getFieldName ).collect (Collectors .joining ());
117+ foreignCompositeKeyEmbedded .setClassName (WordUtils .capitalize (embeddableName ));
118+ foreignCompositeKeyEmbedded .setFieldName (embeddableName );
119+ table .getEmbeddableClasses ().add (foreignCompositeKeyEmbedded ); //add new embeddable to the Table list of Embeddables
120+ setOfForeignKeyColumns .forEach (column -> {
121+ //add the individual foreign keys columns the newly created embeddable
122+ foreignCompositeKeyEmbedded .getColumns ().add (column );
123+ //Remove the individual foreign keys from EmbeddedId and add the newly created embeddable into EmbeddedId
124+ embeddableId .getColumns ().remove (column );
125+ });
126+ Column foreignCompositeField = new Column ();
127+ foreignCompositeField .setType (foreignCompositeKeyEmbedded .getClassName ());
128+ foreignCompositeField .setFieldName (foreignCompositeKeyEmbedded .getFieldName ());
129+ embeddableId .getColumns ().add (foreignCompositeField );
130+
131+ if (embeddableId .getFieldName () != null )
132+ parentTableField .getAnnotations ().add (MapsIdAnnotation .builder ().fieldName (foreignCompositeField .getFieldName ()).build ().toString ());
133+ } else {
134+ //There is no primary Composite key
135+ //If composite foreign key is not inside the composite primary key, then remove it from the table
136+ setOfForeignKeyColumns .forEach (column -> table .getColumns ().remove (column ));
137+ }
138+
97139 Set <JoinColumnAnnotation > joinColumns = new HashSet <>();
140+ //Create the @JoinColumn annotations for the parentTableField
98141 for (int i = 0 ; i < foreignKeyConstraint .getColumns ().size (); i ++) {
99- int finalI = i ;
100- Optional <Column > optionalColumn = table .getColumns ().stream ().filter (column -> column .getName ().equals (foreignKeyConstraint .getColumns ().get (finalI ))).findFirst ();
101- optionalColumn .ifPresent (column -> table .getColumns ().remove (column ));
102-
103142 JoinColumnAnnotation joinColumnAnnotation = JoinColumnAnnotation .builder ().name (foreignKeyConstraint .getColumns ().get (i )).referencedColumnName (foreignKeyConstraint .getReferencedColumns ().get (i )).build ();
104143 joinColumns .add (joinColumnAnnotation );
144+ }
145+ parentTableField .getAnnotations ().add (JoinColumnsAnnotation .builder ().joinColumns (joinColumns ).build ().toString ());
105146
147+ }
148+ //Case: Single Foreign Key
149+ else {
150+ //Get the foreign key column from the table
151+ Optional <Column > optionalColumn = table .getColumns ().stream ().filter (column -> column .getColumnName () != null && column .getColumnName ().equals (foreignKeyConstraint .getReferencedColumns ().get (0 ))).findFirst ();
152+ if (optionalColumn .isPresent ()) {
153+ Column foreignKeyColumn = optionalColumn .get ();
154+ //Check if foreign key is also a primary key, by iterating through the primary key list
155+ Optional <Column > optionalColumnPrimaryForeign = allPrimaryKeyColumns .stream ().filter (column -> column .getColumnName () != null && column .getColumnName ().equals (foreignKeyColumn .getColumnName ())).findFirst ();
156+ optionalColumnPrimaryForeign .ifPresentOrElse (column -> {
157+ //If foreign key is a primary key, don't remove it from table.
158+ //Add a @MapsId annotation to the referenced table field
159+ parentTableField .getAnnotations ().add (MapsIdAnnotation .builder ().fieldName (column .getFieldName ()).build ().toString ());
160+ }, () -> {
161+ //If foreign key is not a primary key, then remove it from the table
162+ optionalColumn .ifPresent (column -> table .getColumns ().remove (column ));
163+ });
106164 }
107- foreignKeyColumn .getAnnotations ().add (JoinColumnsAnnotation .builder ().joinColumns (joinColumns ).build ().toString ());
165+ //Add a @JoinColumn annotation for the referenced table field
166+ parentTableField .getAnnotations ().add (JoinColumnAnnotation .builder ().name (foreignKeyConstraint .getReferencedColumns ().get (0 )).referencedColumnName (foreignKeyConstraint .getReferencedColumns ().get (0 )).build ().toString ());
167+ }
168+ }
108169
109- } else {
110- //Case: Single Foreign Key
111- Optional <Column > optionalColumn = table .getColumns ().stream ().filter (column -> column .getName ().equals (foreignKeyConstraint .getReferencedColumns ().get (0 ))).findFirst ();
112- optionalColumn .ifPresent (column -> table .getColumns ().remove (column ));
113- foreignKeyColumn .getAnnotations ().add (JoinColumnAnnotation .builder ().name (CaseUtils .toCamelCase (foreignKeyConstraint .getReferencedColumns ().get (0 ), false , '_' )).referencedColumnName (foreignKeyConstraint .getReferencedColumns ().get (0 )).build ().toString ());
170+ private static Set <Column > setOfForeignKeys (Table table , ForeignKeyConstraint foreignKeyConstraint ) {
171+ Stream <Column > allTableForeignKeyColumns = table .getColumns ().stream ();
172+ Stream <Column > allEmbeddedForeignKeyColumns = table .getEmbeddableClasses ().stream ().flatMap (embeddableClass -> embeddableClass .getColumns ().stream ());
173+ return Stream .concat (allTableForeignKeyColumns , allEmbeddedForeignKeyColumns ).filter (column -> foreignKeyConstraint .getColumns ().stream ().anyMatch (s -> s .equals (column .getColumnName ()))).collect (Collectors .toSet ());
174+ }
175+
176+ private static Set <Column > getAllPrimaryKeys (Table table , EmbeddableClass embeddableId ) {
177+ //Get set of primary Keys
178+ if (embeddableId != null ){
179+ return embeddableId .getColumns ().stream ().filter (Column ::isPrimaryKey ).collect (Collectors .toSet ());
180+ }else {
181+ return table .getColumns ().stream ().filter (Column ::isPrimaryKey ).collect (Collectors .toSet ());
114182 }
115183 }
116184}
0 commit comments