Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

generate sql script from entity #1202

Closed
wants to merge 9 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,20 @@
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;

import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -499,4 +509,142 @@ public String getFirstEnumValue(String className) {
return null;
}
}

/**
* Get the annotated table name of a given Entity class
*
* @param className
* full qualified class name
* @return the annotated table name if existed or class name without the word Entity
*/
public String getEntityTableName(String className) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a test case for this method to src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/JavaUtilTest.java

if (!className.endsWith("Entity")) {
LOG.error("Could not return table name because {} is not an Entity class", className);
return null;
}
try {
Class<?> entityClass = Class.forName(className);
Table table = entityClass.getAnnotation(Table.class);
return table == null
? StringUtils.left(entityClass.getSimpleName(),
entityClass.getSimpleName().length() - "Entity".length())
: table.name();
} catch (ClassNotFoundException e) {
LOG.error("{}: Could not find {}", e.getMessage(), className);
return null;
}
}

/**
* Helper method to get all fields recursively inclusive fields from super classes
*
* @param cl
* class to find fields
* @param fields
* list of fields to accumulate recursively
* @return list of all fields found
*/
private static List<Field> getAllFields(List<Field> fields, Class<?> cl) {
fields.addAll(Arrays.asList(cl.getDeclaredFields()));

if (cl.getSuperclass() != null) {
getAllFields(fields, cl.getSuperclass());
}

return fields;
}

/**
* Helper method to get type of a field inclusive field from super classes
*
* @param pojoClass
* {@link Class} the class object of the pojo
* @param fieldName
* {@link String} the name of the field
* @return type of the field
*/
private Class<?> getTypeOfField(Class<?> pojoClass, String fieldName) {
if (pojoClass != null) {
List<Field> fields = new ArrayList();
getAllFields(fields, pojoClass);

Optional<Field> field = fields.stream().filter(f -> f.getName().equals(fieldName)).findFirst();

if (field.isPresent()) {
return field.get().getType();
}
}
LOG.error("Could not find type of field {}", fieldName);
return null;
}

/**
* @param pojoClass
* {@link Class} the class object of the pojo
* @param fieldName
* {@link String} the name of the field
* @return true if the field is an instance of java.utils.Collections
*/
public boolean isCollection2(Class<?> pojoClass, String fieldName) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a test case for this method to src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/JavaUtilTest.java

Class<?> type = getTypeOfField(pojoClass, fieldName);
return type == null ? false : Collection.class.isAssignableFrom(type);
}

/**
* @param className
* {@link String} full qualified class name
* @param fieldName
* {@link String} the name of the field
* @return type of the field in String
*/
public String getCanonicalNameOfField(String className, String fieldName) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a test case for this method to src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/JavaUtilTest.java

try {
Class<?> entityClass = Class.forName(className);
Class<?> type = getTypeOfField(entityClass, fieldName);
if (type != null) {
return type.getCanonicalName();
}
} catch (ClassNotFoundException e) {
LOG.error("{}: Could not find {}", e.getMessage(), className);
}
return null;
}

/**
* Get the primary key and its type of a given Entity class
*
* @param className
* full qualified class name
* @return the primary key and its type if found or null
*/
public String getPrimaryKey(String className) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a test case for this method to src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/JavaUtilTest.java

try {
Class<?> entityClass = Class.forName(className);
List<Field> fields = new ArrayList();
getAllFields(fields, entityClass);
for (Field field : fields) {
if (field.isAnnotationPresent(Id.class)) {
return field.getType().getCanonicalName() + ","
+ (field.isAnnotationPresent(Column.class) ? field.getAnnotation(Column.class).name()
: field.getName());
} else {
Optional<Method> getterOptional = Arrays.stream(entityClass.getMethods())
.filter(m -> m.getName().equals(
"get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1))
&& m.isAnnotationPresent(Id.class))
.findFirst();
if (getterOptional.isPresent()) {
Method getter = getterOptional.get();
return getter.getReturnType().getCanonicalName() + ","
+ (getter.isAnnotationPresent(Column.class) ? getter.getAnnotation(Column.class).name()
: field.getName());
}
}
}
} catch (ClassNotFoundException e) {
LOG.error("{}: Could not find {}", e.getMessage(), className);
}
LOG.error("Could not find the field or getter with @Id annotated");
return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<contextConfiguration xmlns="http://capgemini.com/devonfw/cobigen/ContextConfiguration" version="2.1">

<trigger id="sql" type="java" templateFolder="sql">
<matcher type="fqn" value="((.+\.)?([^\.]+))\.([^\.]+)\.dataaccess\.api\.([^\.]+)Entity">
<variableAssignment type="regex" key="entityName" value="5"/>
</matcher>
</trigger>
<trigger id="crud_java_server_app" type="java" templateFolder="crud_java_server_app">
<containerMatcher type="package" value="((.+\.)?([^\.]+))\.([^\.]+)\.dataaccess\.api"
retrieveObjectsRecursively="false"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<#function get_type field>
<#assign type = get_mapping_type(field.canonicalType)>
<#if type?contains("VARCHAR") && field.annotations.javax_validation_constraints_Size?? && field.annotations.javax_validation_constraints_Size.max??>
<#assign type = "VARCHAR(" + field.annotations.javax_validation_constraints_Size.max +")">
</#if>
<#if (field.annotations.javax_persistence_Column??
&& field.annotations.javax_persistence_Column.nullable??
&& field.annotations.javax_persistence_Column.nullable=="false") || field.annotations.javax_validation_constraints_NotNull?? >
<#assign type = type + " NOT NULL">
</#if>
<#return type>
</#function>

<#function get_mapping_type input_type>
<#if input_type?contains("Integer") || input_type=="int" || input_type?contains("Year") || input_type?contains("Month") || JavaUtil.isEnum(input_type)>
<#assign type = "INTEGER">
<#elseif input_type?contains("Long") || input_type=="long" || input_type?contains("Object")>
<#assign type = "BIGINT">
<#elseif input_type?contains("Short") || input_type=="short">
<#assign type = "SMALLINT">
<#elseif input_type?contains("Float") || input_type=="float">
<#assign type = "FLOAT">
<#elseif input_type?contains("Double") || input_type=="double">
<#assign type = "DOUBLE">
<#elseif input_type?contains("BigDecimal") || input_type?contains("BigInteger")>
<#assign type = "NUMERIC">
<#elseif input_type?contains("Character") || input_type=="char">
<#assign type = "CHAR">
<#elseif input_type?contains("Byte") || input_type=='byte'>
<#assign type = "TINYINT">
<#elseif input_type?contains("Boolean") || input_type=="boolean">
<#assign type = "BOOLEAN">
<#elseif input_type?contains("Instant") || input_type?contains("Timestamp")>
<#assign type = "TIMESTAMP">
<#elseif input_type?contains("Date") || input_type?contains("Calendar")>
<#assign type = "DATE">
<#elseif input_type?contains("Time")>
<#assign type = 'TIME'>
<#elseif input_type?contains("UUID")>
<#assign type = "BINARY">
<#elseif input_type?contains("Blob")>
<#assign type = "BLOB">
<#else>
<#assign type = "VARCHAR">
</#if>
<#return type>
</#function>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<templatesConfiguration xmlns="http://capgemini.com/devonfw/cobigen/TemplatesConfiguration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.1">

<templates>
<template name="create_table" templateFile="templates/V0000__Create_${variables.entityName}.sql.ftl"
destinationPath="src/main/resources/db/type/h2/V0000__Create_${variables.entityName}.sql" mergeStrategy="override"/>
</templates>

<increments>
<increment name="sql" description="SQL">
<templateRef ref="create_table"/>
</increment>
</increments>
</templatesConfiguration>
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<#include '/functions.ftl'>
<#assign tableName = JavaUtil.getEntityTableName(pojo.canonicalName)>
CREATE TABLE ${tableName} (
<#assign fkList = []>
<#assign columns = []>
<#assign refTables = []>
<#list pojo.methodAccessibleFields as field>
<#if !field.annotations.javax_persistence_Transient??>
<#if field.annotations.javax_persistence_Column?? && field.annotations.javax_persistence_Column.name?has_content>
<#assign name = field.annotations.javax_persistence_Column.name>
<#else>
<#assign name = field.name>
</#if>
<#--Field: primary key-->
<#if field.annotations.javax_persistence_Id??>
<#assign pk = name>
<#assign type = get_type(field)>
<#if !type?contains("NOT NULL")>
<#assign type = type + " NOT NULL">
</#if>
<#if field.annotations.javax_persistence_GeneratedValue??
&& field.annotations.javax_persistence_GeneratedValue.strategy??>
<#assign type = type + " AUTO_INCREMENT">
</#if>
<#assign columns = columns + [{"name": name, "type":type}]>
<#elseif !JavaUtil.isCollection2(classObject, field.name)>
<#--Field: simple entity-->
<#if field.type?ends_with("Entity")>
<#if field.annotations.javax_persistence_JoinColumn?? && field.annotations.javax_persistence_JoinColumn.referencedColumnName?has_content>
<#assign id = field.annotations.javax_persistence_JoinColumn.referencedColumnName>
<#assign type = get_mapping_type(JavaUtil.getCanonicalNameOfField(field.canonicalType, id))>
<#else>
<#assign pkReceived = JavaUtil.getPrimaryKey(field.canonicalType)?split(",")>
<#assign type = get_mapping_type(pkReceived[0])>
<#assign id = pkReceived[1]>
</#if>
<#if field.annotations.javax_persistence_JoinColumn?? && field.annotations.javax_persistence_JoinColumn.name?has_content>
<#assign name = field.annotations.javax_persistence_JoinColumn.name>
<#else>
<#assign name = name + "_" + id>
</#if>
<#if field.annotations.javax_persistence_JoinColumn??
&& (field.annotations.javax_persistence_ManyToOne?? || field.annotations.javax_persistence_OneToOne??)>
<#assign tableReceived = JavaUtil.getEntityTableName(field.canonicalType)>
<#if tableReceived?has_content>
<#assign table = tableReceived>
<#else>
<#assign table = field.type>
</#if>
<#assign fkList = fkList + [{"key": name, "table": table, "id": id}]>
</#if>
<#else>
<#--Field: primitive-->
<#assign type = get_type(field)/>
</#if>
<#assign columns = columns + [{"name": name, "type":type}]>
<#else>
<#if field.annotations.javax_persistence_ManyToMany?? && field.annotations.javax_persistence_JoinTable??>
<#--Field: collection of entity-->
<#assign entity = field.canonicalType?substring(field.canonicalType?index_of("<") + 1,field.canonicalType?length - 1)>
<#assign entityTable = JavaUtil.getEntityTableName(entity)>
<#assign table1 = tableName>
<#assign table2 = entityTable>
<#if field.annotations.javax_persistence_JoinTable.name?has_content>
<#assign refTableName = field.annotations.javax_persistence_JoinTable.name>
<#else>
<#assign refTableName = table1 + "_" + table2>
</#if>
<#--not yet support multiple JoinColumns or no JoinColumn-->
<#if field.annotations.javax_persistence_JoinTable.joinColumns?has_content
&& field.annotations.javax_persistence_JoinTable.joinColumns?is_enumerable
&& field.annotations.javax_persistence_JoinTable.joinColumns[0]?has_content>
<#assign col = field.annotations.javax_persistence_JoinTable.joinColumns[0].javax_persistence_JoinColumn>
<#assign name1 = col.name>
<#if col.referencedColumnName?has_content>
<#assign id1 = col.referencedColumnName>
<#assign type1 = get_mapping_type(JavaUtil.getCanonicalNameOfField(pojo.canonicalName, id1))>
<#else>
<#assign result = JavaUtil.getPrimaryKey(pojo.canonicalName)?split(",")>
<#assign type1 = get_mapping_type(result[0])>
<#assign id1 = result[1]>
</#if>
<#else>
<#continue>
</#if>
<#if field.annotations.javax_persistence_JoinTable.inverseJoinColumns?has_content
&& field.annotations.javax_persistence_JoinTable.inverseJoinColumns?is_enumerable
&& field.annotations.javax_persistence_JoinTable.inverseJoinColumns[0]?has_content>
<#assign col = field.annotations.javax_persistence_JoinTable.inverseJoinColumns[0].javax_persistence_JoinColumn>
<#assign name2 = col.name>
<#if col.referencedColumnName?has_content>
<#assign id2 = col.referencedColumnName>
<#assign type2 = get_mapping_type(JavaUtil.getCanonicalNameOfField(entity, id2))>
<#else>
<#assign result = JavaUtil.getPrimaryKey(entity)?split(",")>
<#assign type2 = get_mapping_type(result[0])>
<#assign id2 = result[1]>
</#if>
<#else>
<#continue>
</#if>
<#assign refTables = refTables + [{"table": refTableName, "columns":[{"name": name1, "id": id1, "type": type1, "table": table1}, {"name": name2, "id": id2, "type": type2, "table": table2}] }]>
</#if>
</#if>
</#if>
</#list>
<#list columns as col>
${col.name?right_pad(30)} ${col.type},
</#list>
CONSTRAINT PK_${tableName} PRIMARY KEY(${pk}),
<#list fkList as fk>
CONSTRAINT FK_${tableName}_${fk.key} FOREIGN KEY(${fk.key}) REFERENCES ${fk.table}(${fk.id}),
</#list>
);
<#list refTables as tb>

CREATE TABLE ${tb.table} (
<#assign col1 = tb.columns[0]>
<#assign col2 = tb.columns[1]>
${col1.name?right_pad(30)} ${col1.type} NOT NULL,
${col2.name?right_pad(30)} ${col2.type} NOT NULL,
CONSTRAINT PK_${tb.table} PRIMARY KEY(${col1.name}, ${col2.name}),
CONSTRAINT FK_${tb.table}_${col1.name} FOREIGN KEY(${col1.name}) REFERENCES ${col1.table}(${col1.id}),
CONSTRAINT FK_${tb.table}_${col2.name} FOREIGN KEY(${col2.name}) REFERENCES ${col2.table}(${col2.id}),
);
</#list>
Loading