diff --git a/cobigen-templates/templates-devon4j/.gitignore b/cobigen-templates/templates-devon4j/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cobigen-templates/templates-devon4j/pom.xml b/cobigen-templates/templates-devon4j/pom.xml index 9bcaece44e..e2ff65a882 100644 --- a/cobigen-templates/templates-devon4j/pom.xml +++ b/cobigen-templates/templates-devon4j/pom.xml @@ -54,6 +54,21 @@ 3.10.0 test + + jakarta.validation + jakarta.validation-api + 3.0.2 + + + com.devonfw.cobigen + javaplugin + test + + + com.devonfw.cobigen + tempeng-freemarker + test + diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/JavaUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/JavaUtil.java index ced2f5fde2..f1bb367ccc 100644 --- a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/JavaUtil.java +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/JavaUtil.java @@ -31,7 +31,7 @@ public JavaUtil() { /** * Returns the Object version of a Java primitive or the input if the input isn't a java primitive - * + * * @param simpleType String * @return the corresponding object wrapper type simple name of the input if the input is the name of a primitive java * type. The input itself if not. (e.g. "int" results in "Integer") @@ -62,7 +62,7 @@ public String boxJavaPrimitives(Class pojoClass, String fieldName) throws NoS if (pojoClass == null) { throw new IllegalAccessError( - "Class object is null. Cannot generate template as it might obviously depend on reflection."); + "Class object is null. Cannot generate template as it might obviously depend on reflection."); } if (equalsJavaPrimitive(pojoClass, fieldName)) { @@ -122,7 +122,7 @@ public boolean equalsJavaPrimitiveOrWrapper(String simpleType) { * @throws SecurityException if the field cannot be accessed. */ public boolean equalsJavaPrimitive(Class pojoClass, String fieldName) - throws NoSuchFieldException, SecurityException { + throws NoSuchFieldException, SecurityException { if (pojoClass == null) { return false; @@ -169,10 +169,10 @@ public boolean equalsJavaPrimitiveIncludingArrays(String simpleType) { * @throws SecurityException if the field cannot be accessed. */ public boolean equalsJavaPrimitiveIncludingArrays(Class pojoClass, String fieldName) - throws NoSuchFieldException, SecurityException { + throws NoSuchFieldException, SecurityException { return equalsJavaPrimitive(pojoClass, fieldName) || (pojoClass.getDeclaredField(fieldName).getType().isArray() - && pojoClass.getDeclaredField(fieldName).getType().getComponentType().isPrimitive()); + && pojoClass.getDeclaredField(fieldName).getType().getComponentType().isPrimitive()); } /** @@ -206,7 +206,7 @@ public String castJavaPrimitives(String simpleType, String varName) throws Class * @throws SecurityException if the field cannot be accessed. */ public String castJavaPrimitives(Class pojoClass, String fieldName) - throws NoSuchFieldException, SecurityException { + throws NoSuchFieldException, SecurityException { if (equalsJavaPrimitive(pojoClass, fieldName)) { return String.format("((%1$s)%2$s)", boxJavaPrimitives(pojoClass, fieldName), fieldName); @@ -334,7 +334,7 @@ public String getReturnType(Class pojoClass, String methodName) throws NoSuch if (pojoClass == null) { throw new IllegalAccessError( - "Class object is null. Cannot generate template as it might obviously depend on reflection."); + "Class object is null. Cannot generate template as it might obviously depend on reflection."); } String s = "-"; Method method = findMethod(pojoClass, methodName); @@ -357,11 +357,11 @@ public String getReturnType(Class pojoClass, String methodName) throws NoSuch */ @SuppressWarnings("unchecked") public String getReturnTypeOfMethodAnnotatedWith(Class pojoClass, String annotatedClassName) - throws ClassNotFoundException { + throws ClassNotFoundException { if (pojoClass == null) { throw new IllegalAccessError( - "Class object is null. Cannot generate template as it might obviously depend on reflection."); + "Class object is null. Cannot generate template as it might obviously depend on reflection."); } Method[] methods = pojoClass.getDeclaredMethods(); @@ -414,7 +414,7 @@ private Method findMethod(Class pojoClass, String methodName) { if (pojoClass == null) { throw new IllegalAccessError( - "Class object is null. Cannot generate template as it might obviously depend on reflection."); + "Class object is null. Cannot generate template as it might obviously depend on reflection."); } for (Method m : pojoClass.getMethods()) { if (m.getName().equals(methodName)) { diff --git a/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java new file mode 100644 index 0000000000..2795d835b1 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/main/java/com/devonfw/cobigen/templates/devon4j/utils/SQLUtil.java @@ -0,0 +1,300 @@ +package com.devonfw.cobigen.templates.devon4j.utils; + +import java.util.*; +import java.util.function.Function; + +/** + * Provides operations to identify and process SQL specific information + * + */ +public class SQLUtil { + + private static int DEFAULT_FIELD_LENGTH = 255; + + /** + * The constructor. + */ + public SQLUtil() { + + // Empty for CobiGen to automatically instantiate it + } + + /** + * Unwraps type to autogenerate a name for the table following devonfw naming convention. + * + * @param entityType String that represents the entity class type + * @return parsed table name + */ + public String tableName(String entityType) { + + return entityType.replaceFirst(".*<", "").replaceFirst(">.*", "").replace("Entity", "").toUpperCase(); + } + + /** + * Parses a @JoinColumn annotation directly into a Foreign Key statement for a @JoinTable + * + * @param joinColumnAnnotation + * @param defaultTableName Possible to pass TableName in case it's not specified in the annotation and has to be + * implied from context + * @return column + foreign key constraint for this @JoinColumn annotation + */ + public String parseJoinColumn(Map joinColumnAnnotation, String defaultTableName) { + + Function extract = (fieldKey) -> Objects.requireNonNull(getValue(joinColumnAnnotation, fieldKey)); + String name = extract.apply("name"); + boolean nullable = Boolean.parseBoolean(extract.apply("nullable")); + boolean unique = Boolean.parseBoolean(extract.apply("unique")); + String table = extract.apply("table"); + String referencedColumnName = extract.apply("referencedColumnName"); + // Check if some fields haven't been defined and replace with defaults + if (referencedColumnName.equals("")) { + referencedColumnName = "ID"; + } + if (table.equals("")) { + table = defaultTableName; + } + if (table.equals("")) { + throw new IllegalStateException( + "Cannot infer name for reference table! Error encountered while parsing JoinColumnAnnotation: " + + joinColumnAnnotation.toString()); + } + // If the name is empty build the fieldName by appending ID to the tableName + if (name.equals("")) { + name = table + "_ID"; + } + String statement = name + " BIGINT"; + if (unique) { + statement += " UNIQUE"; + } + if (!nullable) { + statement += " NOT NULL"; + } + String foreignKeyDef = String.format("FOREIGN KEY (%s) REFERENCES %s(%s)", name, table, referencedColumnName); + + return statement + ", " + foreignKeyDef; + } + + /** + * Generates a primary key statement from the given field + * @param field Dynamic hashmap containing field data + * @return SQL Primary key statement for table as String + */ + public String primaryKeyStatement(Map field) { + + String fieldName = getFieldName(field); + Map annotations = getValue(field, "annotations"); + Map columnAnnotations = getValue(annotations, "javax_persistence_Column"); + // Check for @Column to override default fieldname + if (columnAnnotations != null) { + String columnFieldName = getValue(columnAnnotations, "name"); + if (columnFieldName != null) + fieldName = columnFieldName; + } + String incrementType = "AUTO_INCREMENT"; + return String.format("%s BIGINT %s PRIMARY KEY", fieldName, incrementType); + } + + /** + * Generates a foreign key statement from the given field + * @param field Dynamic hashmap containing field data + * @return SQL Foreign key statement as String + */ + public String foreignKeyStatement(Map field) { + + Map annotations = getValue(field, "annotations"); + Map joinColumnAnnotation = getValue(annotations, "javax_persistence_JoinColumn"); + String fieldName = Objects.requireNonNull(getValue(field, "name")), fieldType = "BIGINT", + refTable = Objects.requireNonNull(getValue(field, "type")), referencedColumnName = "id"; + Boolean unique = false, nullable = true; + + // Try extracting tablename from type following devonfw naming conventions: AwdeEntity -> AWDE + refTable = refTable.replace("Entity", "").toUpperCase(); + + // Try autogenerating foreign key name through naming convention + fieldName = fieldName.replace("Entity", "").toUpperCase() + "_ID"; + + // Parse @JoinColumn values and override defaults if values are present + if (joinColumnAnnotation != null) { + // Each field is extracted and controlled, if present override the defaults. + String nameOverride = getValue(joinColumnAnnotation, "name"); + if (!Objects.equals(nameOverride, "")) + fieldName = nameOverride; + + String tableOverride = getValue(joinColumnAnnotation, "table"); + if (!Objects.equals(tableOverride, "")) + refTable = tableOverride; + + String refColOverride = getValue(joinColumnAnnotation, "referencedColumnName"); + if (!Objects.equals(refColOverride, "")) + referencedColumnName = refColOverride; + + unique = isUnique(joinColumnAnnotation); + nullable = isNullable(joinColumnAnnotation); + } + // Build column definition + String columnDef = fieldName + " " + fieldType; + if (unique) + columnDef += " UNIQUE"; + if (!nullable) + columnDef += " NOT NULL"; + // Build Foreign Key constraint and append it to column definition + String foreignKeyDef = String.format("FOREIGN KEY (%s) REFERENCES %s(%s)", fieldName, refTable, + referencedColumnName); + return columnDef + ", " + foreignKeyDef; + } + + /** + * Basic SQL column statements derived from field hashmaps + * @param field Dynamic hashmap with field data + * @return Basic SQL statement as String + */ + public String basicStatement(Map field) { + + Map columnAnnotation = chainAccess(field, new String[] { "annotations", "javax_persistence_Column" }); + String fieldName = getFieldName(field), typeString = Objects.requireNonNull(getValue(field, "type")), + fieldType = mapType(typeString); + Integer fieldLength = DEFAULT_FIELD_LENGTH; + boolean nullable = true, unique = false; + // Try to infer fieldType from possible annotations + Map enumerateAnnotation = chainAccess(field, + new String[] { "annotations", "javax_persistence_Enumerated" }); + if (enumerateAnnotation != null) { + String enumType = Objects.requireNonNull(getValue(enumerateAnnotation, "value")); + if (enumType.equals("STRING")) { + fieldType = "VARCHAR"; + } else { + fieldType = "INTEGER"; + } + } + // Parse @Column if present + if (columnAnnotation != null) { + fieldLength = Integer.parseInt(Objects.requireNonNull(getValue(columnAnnotation, "length"))); + nullable = isNullable(columnAnnotation); + unique = isUnique(columnAnnotation); + } + + // If fieldType is still empty throw exception + if (fieldType == null) + throw new IllegalArgumentException("Couldn't map Java type to SQL type for typeString: " + typeString); + + // Add size to VARCHAR fields + if (fieldType.equals("VARCHAR")) { + fieldType = String.format("VARCHAR(%d)", fieldLength); + } + String statement = String.format("%s %s", fieldName, fieldType); + if (!nullable) + statement += " NOT NULL"; + if (unique) + statement += " UNIQUE"; + return statement; + } + + /** + * Extracts value of nullable from @Column and @JoinColumn annotations + * + * @param columnAnnotation Map for the Column and JoinColumn annotations + */ + private static boolean isNullable(Map columnAnnotation) { + + return Boolean.parseBoolean(getValue(columnAnnotation, "nullable")); + } + + /** + * Extracts value of unique from @Column and @JoinColumn annotations + * + * @param columnAnnotation Map for the Column and JoinColumn annotations + */ + private static boolean isUnique(Map columnAnnotation) { + + return Boolean.parseBoolean(getValue(columnAnnotation, "unique")); + } + + /** + * Helper function to map simple Java types to SQL types, returns null on unmappable type + * @param typeString JavaType as String (int, Long, String...) + * @return SQLType as String + */ + public static String mapType(String typeString) { + + // Shortcut for case insensitive regex matching with start and ending ignore + Function match = (regex) -> typeString.matches("(?i).*" + "(" + regex + ")" + ".*"); + if (match.apply("(integer)|(int)")) { + return "INTEGER"; + } else if (match.apply("long")) { + return "BIGINT"; + } else if (match.apply("short")) { + return "SMALLINT"; + } else if (match.apply("BigDecimal")) { + return "NUMERIC"; + } else if (match.apply("String")) { + return "VARCHAR"; + } else if (match.apply("(char)|(Character)")) { + return "CHAR(1)"; + } else if (match.apply("byte\\[\\]")) { + return "BLOB"; + } else if (match.apply("byte")) { + return "TINYINT"; + } else if (match.apply("boolean")) { + return "BIT"; + } else if (match.apply("Date")) { + return "DATE"; + } else if (match.apply("(Class)|(Locale)|(TimeZone)|(Currency)")) { + return "VARCHAR"; + } else if (match.apply("(Timestamp)|(Calendar)")) { + return "TIMESTAMP"; + } else if (match.apply("Time")) { + return "TIME"; + } else { + return null; + } + } + + /** + * Extracts the name of the field from the Map whilst checking for name-override in @Column annotation + * @param field Dynamic map for field + * @return simple name for field as String + */ + static private String getFieldName(Map field) { + + String fieldName = chainAccess(field, new String[] { "annotations", "javax_persistence_Column", "name" }); + if (fieldName != null && !fieldName.equals("")) { + return fieldName; + } else { + return Objects.requireNonNull(getValue(field, "name")); + } + } + + /** + * Helper function to navigate nested maps dynamically. Returns null on any type of error + * @param map Dynamic map from which to extract data + * @param nestedFields ordered array of fields that need to be navigated in the map + * @return value if found, null otherwise (both on casting errors and value not found) + */ + static private T chainAccess(Map map, String[] nestedFields) { + + try { + for (int i = 0; i < nestedFields.length - 1; i++) { + String key = nestedFields[i]; + map = getValue(map, key); + } + return (T) getValue(map, nestedFields[nestedFields.length - 1]); + } catch (Exception ignored) { + return null; + } + } + + /** + * Parametrized helper function to dynamically extract data from a map. Returns null on casting errors + * @param map Dynamic map from which to extract data + * @param key key for the value + * @return value if found and cast succeeds, null otherwise + */ + static private T getValue(Map map, String key) { + + try { + return (T) map.get(key); + } catch (Exception ignored) { + return null; + } + } +} diff --git a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/context.xml b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/context.xml new file mode 100644 index 0000000000..7dc46c7425 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/context.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates.xml b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates.xml new file mode 100644 index 0000000000..d3952f36f2 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl new file mode 100644 index 0000000000..91c519eec9 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl @@ -0,0 +1,73 @@ +<#if pojo.annotations.javax_persistence_Table??> + <#assign tableName = pojo.annotations.javax_persistence_Table.name> +<#else> + <#assign tableName = SQLUtil.tableName(pojo.name)> + +<#assign statements = []> +<#assign joinTables = []> +<#list pojo.methodAccessibleFields as field> +<#-- Skip Transient fields --> + <#if field.annotations.javax_persistence_Transient??> + <#continue> + <#-- Primary Key statement --> + <#elseif field.annotations.javax_persistence_Id??> + <#assign statements += [SQLUtil.primaryKeyStatement(field)]> + <#-- OneToOne statement --> + <#elseif field.annotations.javax_persistence_OneToOne??> + <#-- Key mapped on other table, skip --> + <#if field.annotations.javax_persistence_OneToOne.mappedBy != ""> + <#continue> + + <#assign statements += [SQLUtil.foreignKeyStatement(field)]> + <#-- Skip OneToMany as it's just a Foreign Key on a different table --> + <#elseif field.annotations.javax_persistence_OneToMany??> + <#continue> + <#-- ManyToOne: create Foreign Keystatement from the field --> + <#elseif field.annotations.javax_persistence_ManyToOne??> + <#assign statements += [SQLUtil.foreignKeyStatement(field)]> + <#-- TODO: Check if there is a JoinTable specified that should be created. --> + <#elseif field.annotations.javax_persistence_ManyToMany?? && field.annotations.javax_persistence_JoinTable??> + <#assign joinTableAnnotation = field.annotations.javax_persistence_JoinTable> + + <#-- Parse joinColumns to generate Foreign Keys --> + <#assign joinColumns = joinTableAnnotation.joinColumns> + <#assign inverseJoinColumns = joinTableAnnotation.inverseJoinColumns> + <#-- Statement collector list --> + <#assign statements = [] > + <#assign defaultFieldTable = SQLUtil.tableName(field.type)> + <#list joinColumns as jcol> + <#assign jcolAnnotation = jcol.javax_persistence_JoinColumn> + <#assign statements += [SQLUtil.parseJoinColumn(jcolAnnotation, defaultFieldTable)]> + + + <#-- When parsing inverse join columns pass the tableName as a default for the reference to this parsed Entity --> + <#list inverseJoinColumns as jcol> + <#assign jcolAnnotation = jcol.javax_persistence_JoinColumn> + <#assign statements += [SQLUtil.parseJoinColumn(jcolAnnotation, tableName)]> + + <#-- Build joinTable with parsed data --> + <#assign joinTable = {}> + <#assign joinTable += { "statements": statements }> + <#assign joinTable += { "name": joinTableAnnotation.name }> + <#-- Append result to collector list --> + <#assign joinTables += [joinTable]> + + <#-- Try generating simple SQL statement from field --> + <#else> + <#assign statements += [SQLUtil.basicStatement(field)]> + + +CREATE TABLE ${tableName} ( +<#list statements as statement> + ${statement}, + +); +<#-- TODO: parse generated JoinTables --> +<#list joinTables as tb> +CREATE TABLE ${tb.name} ( + ID BIGINT AUTO_INCREMENT PRIMARY KEY, +<#list tb.statements as statement> + ${statement}, + +); + \ No newline at end of file diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/AbstractJavaTemplateTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/AbstractJavaTemplateTest.java new file mode 100644 index 0000000000..7f94480974 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/AbstractJavaTemplateTest.java @@ -0,0 +1,113 @@ +package com.devonfw.cobigen.templates.devon4j.test.templates; + +import com.devonfw.cobigen.api.extension.TextTemplate; +import com.devonfw.cobigen.api.extension.TextTemplateEngine; +import com.devonfw.cobigen.javaplugin.inputreader.JavaInputReader; +import com.devonfw.cobigen.tempeng.freemarker.FreeMarkerTemplateEngine; +import org.junit.Before; + +import java.io.StringWriter; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +public abstract class AbstractJavaTemplateTest { + /** + * Model used by freemarker + */ + public Map model; + /** + * Engine that will generate output from model and template + */ + public FreeMarkerTemplateEngine engine; + /** + * Implementation of TextTemplate for template processing + */ + public TextTemplate template; + + /** + * Creates an anonymous TextTemplate object that works on the given template files + * @param relativePath Relative Path to the template from the /templates folder, most likely coincides with template + * filename + * @param relativeAbsolutePath Relative path from source root to the template + * @return anonymous instance of TextTemplate that holds the data in Overridden interface methods + */ + public TextTemplate createTemplate(String relativePath, Path relativeAbsolutePath) { + + return new TextTemplate() { + @Override + public String getRelativeTemplatePath() { + + return relativePath; + } + + @Override + public Path getAbsoluteTemplatePath() { + + return relativeAbsolutePath; + } + }; + } + + /** + * Creates a template engine for the given template path + * + * @param templateFolderPath Relative path to template folder from project source root + * @return {@link TextTemplateEngine} implementation for Apache FreeMarker. + */ + public FreeMarkerTemplateEngine createEngine(String templateFolderPath) { + + FreeMarkerTemplateEngine engine = new FreeMarkerTemplateEngine(); + engine.setTemplateFolder(Paths.get(templateFolderPath)); + return engine; + } + + /** + * Instanciates a class of the given Type and adds it to the model with it's simplename. + * (Just like all Utils following the naming convention) + * @param clazz Class with a public NoArgsConstructor to instanciate it + */ + public void addUtil(Class clazz) { + try { + String name = clazz.getSimpleName(); + Object instance = clazz.getConstructor().newInstance(); + model.put(name, instance); + } catch (Exception exception) { + exception.printStackTrace(); + throw new RuntimeException("Failed adding the Util to the Model, please check the error stacktrace and fix it or add the util manually!"); + } + + } + + /** + * Consumes the given class to produce the template + * @param modelClass Class to auto-generate reflective pojo model + * @return Produced file + */ + public String process(Class modelClass) { + Path tp = Paths.get(getTemplatePath()); + String filename = tp.getFileName().toString(); + Path templateFolder = tp.getParent(); + model = new JavaInputReader().createModel(modelClass); + template = createTemplate(filename, templateFolder); + engine = new FreeMarkerTemplateEngine(); + engine.setTemplateFolder(templateFolder); + for (Class utilClass : getUtils()) { + addUtil(utilClass); + } + StringWriter out = new StringWriter(); + engine.process(template, model, out, "UTF-8"); + return out.toString(); + } + + /** + * @return utils Classes to auto-instanciate and inject into freemarker for the template + */ + abstract public Class[] getUtils(); + + /** + * @return templatePath Path to the template relative to subproject source root + */ + abstract public String getTemplatePath(); +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java new file mode 100644 index 0000000000..0c2ae96324 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/SQLTemplateGenerationTest.java @@ -0,0 +1,78 @@ +package com.devonfw.cobigen.templates.devon4j.test.templates; + +import static org.assertj.core.api.Assertions.*; + +import com.devonfw.cobigen.templates.devon4j.test.templates.testclasses.SQLTestJoinTableEntity; +import org.junit.Test; + +import com.devonfw.cobigen.templates.devon4j.test.templates.testclasses.SQLTestEntity; +import com.devonfw.cobigen.templates.devon4j.test.templates.testclasses.SQLTestDataTypesEntity; +import com.devonfw.cobigen.templates.devon4j.test.templates.testclasses.SQLTestForeignKeysEntity; +import com.devonfw.cobigen.templates.devon4j.utils.SQLUtil; + +/** + * Test class for SQL template generation + * + */ +public class SQLTemplateGenerationTest extends AbstractJavaTemplateTest { + + @Override + public Class[] getUtils() { + + return new Class[] { SQLUtil.class }; + } + + @Override + public String getTemplatePath() { + + return "src/main/templates/sql_java_app/templates/V0000__Create_${variables.entityName}Entity.sql.ftl"; + } + + /** + * Tests the correct generation of the enumerated type, the primary key, and name overriding + */ + @Test + public void testEnumType() { + + String output = process(SQLTestEntity.class); + assertThat(output).contains("CREATE TABLE SQLTEST").contains("ENUM_TEST_FIELD_NAME_OVERRIDE VARCHAR(420)") + .contains("MY_ID_FIELD BIGINT AUTO_INCREMENT PRIMARY KEY"); + } + + /** + * Tests the correct generation of data types + */ + @Test + public void testDatatypeMapping() { + + String ouptut = process(SQLTestDataTypesEntity.class); + assertThat(ouptut).contains("timestamp2 TIMESTAMP").contains("bit BIT,").contains("date DATE") + .contains("tinyint TINYINT").contains("integer2 INTEGER").contains("bigint BIGINT") + .contains("varchar3 VARCHAR(255)").contains("integer1 INTEGER").contains("varchar4 VARCHAR(255)") + .contains("blob BLOB").contains("varchar VARCHAR(255)").contains("char2 CHAR(1)").contains("smallint SMALLINT") + .contains("char1 CHAR(1)").contains("timestamp TIMESTAMP").contains("time TIME").contains("numeric NUMERIC") + .contains("varchar2 VARCHAR(255)").contains("CREATE TABLE SQLDataTypeTest").contains("varchar5 VARCHAR(255)"); + + } + + /** + * Tests the correct generation of foreign key statements + */ + @Test + public void testForeignKeyStatements() { + + String output = process(SQLTestForeignKeysEntity.class); + assertThat(output).contains("test_id BIGINT, FOREIGN KEY (test_id) REFERENCES SQLTEST(MY_ID_FIELD)"); + } + + /** + * Tests successful generation of a second CREATE TABLE statement from the @JoinTable annotation + */ + @Test + public void testJoinTableGeneration() { + String output = process(SQLTestJoinTableEntity.class); + assertThat(output).contains("CREATE TABLE MY_AWESOME_JOINTABLE") + .contains("REF_ENTITY_ID BIGINT UNIQUE, FOREIGN KEY (REF_ENTITY_ID) REFERENCES REFERENCE(OVERRIDE_ID)") + .contains("SQLTESTJOINTABLE_ID BIGINT, FOREIGN KEY (SQLTESTJOINTABLE_ID) REFERENCES SQLTESTJOINTABLE(ID)"); + } +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/EnumForTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/EnumForTest.java new file mode 100644 index 0000000000..b504f0b8d4 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/EnumForTest.java @@ -0,0 +1,7 @@ +package com.devonfw.cobigen.templates.devon4j.test.templates.testclasses; + +public enum EnumForTest { + VALUE, + OK, + MAKING_THINGS_UP +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/ReferenceEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/ReferenceEntity.java new file mode 100644 index 0000000000..329b33b71c --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/ReferenceEntity.java @@ -0,0 +1,19 @@ +package com.devonfw.cobigen.templates.devon4j.test.templates.testclasses; + +import javax.persistence.*; + +@Entity +public class ReferenceEntity { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestDataTypesEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestDataTypesEntity.java new file mode 100644 index 0000000000..fcf9d51637 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestDataTypesEntity.java @@ -0,0 +1,385 @@ +package com.devonfw.cobigen.templates.devon4j.test.templates.testclasses; + +import java.math.BigDecimal; +import java.security.Timestamp; +import java.sql.Time; +import java.util.Calendar; +import java.util.Currency; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * Test class to test sql data type mapping + * + */ +@Entity +@Table(name = "SQLDataTypeTest") +public class SQLTestDataTypesEntity { + + @Column() + private int integer1; + + @Column() + private Integer integer2; + + @Column() + private long bigint; + + @Column() + private short smallint; + + @Column() + private BigDecimal numeric; + + @Column() + private String varchar; + + @Column() + private char char1; + + @Column() + private Character char2; + + @Column() + private byte tinyint; + + @Column() + private boolean bit; + + @Column() + private Date date; + + @Column() + private Time time; + + @Column() + private Timestamp timestamp; + + @Column() + private Calendar timestamp2; + + @Column() + private byte[] blob; + + @Column() + private Class varchar2; + + @Column() + private Locale varchar3; + + @Column() + private TimeZone varchar4; + + @Column() + private Currency varchar5; + + /** + * @return integer1 + */ + public int getInteger1() { + + return this.integer1; + } + + /** + * @param integer1 new value of {@link #getinteger1}. + */ + public void setInteger1(int integer1) { + + this.integer1 = integer1; + } + + /** + * @return integer2 + */ + public Integer getInteger2() { + + return this.integer2; + } + + /** + * @param integer2 new value of {@link #getinteger2}. + */ + public void setInteger2(Integer integer2) { + + this.integer2 = integer2; + } + + /** + * @return bigint + */ + public long getBigint() { + + return this.bigint; + } + + /** + * @param bigint new value of {@link #getbigint}. + */ + public void setBigint(long bigint) { + + this.bigint = bigint; + } + + /** + * @return smallint + */ + public short getSmallint() { + + return this.smallint; + } + + /** + * @param smallint new value of {@link #getsmallint}. + */ + public void setSmallint(short smallint) { + + this.smallint = smallint; + } + + /** + * @return numeric + */ + public BigDecimal getNumeric() { + + return this.numeric; + } + + /** + * @param numeric new value of {@link #getnumeric}. + */ + public void setNumeric(BigDecimal numeric) { + + this.numeric = numeric; + } + + /** + * @return varchar + */ + public String getVarchar() { + + return this.varchar; + } + + /** + * @param varchar new value of {@link #getvarchar}. + */ + public void setVarchar(String varchar) { + + this.varchar = varchar; + } + + /** + * @return char1 + */ + public char getChar1() { + + return this.char1; + } + + /** + * @param char1 new value of {@link #getchar1}. + */ + public void setChar1(char char1) { + + this.char1 = char1; + } + + /** + * @return char2 + */ + public Character getChar2() { + + return this.char2; + } + + /** + * @param char2 new value of {@link #getchar2}. + */ + public void setChar2(Character char2) { + + this.char2 = char2; + } + + /** + * @return tinyint + */ + public byte getTinyint() { + + return this.tinyint; + } + + /** + * @param tinyint new value of {@link #gettinyint}. + */ + public void setTinyint(byte tinyint) { + + this.tinyint = tinyint; + } + + /** + * @return bit + */ + public boolean isBit() { + + return this.bit; + } + + /** + * @param bit new value of {@link #getbit}. + */ + public void setBit(boolean bit) { + + this.bit = bit; + } + + /** + * @return date + */ + public Date getDate() { + + return this.date; + } + + /** + * @param date new value of {@link #getdate}. + */ + public void setDate(Date date) { + + this.date = date; + } + + /** + * @return time + */ + public Time getTime() { + + return this.time; + } + + /** + * @param time new value of {@link #gettime}. + */ + public void setTime(Time time) { + + this.time = time; + } + + /** + * @return timestamp + */ + public Timestamp getTimestamp() { + + return this.timestamp; + } + + /** + * @param timestamp new value of {@link #gettimestamp}. + */ + public void setTimestamp(Timestamp timestamp) { + + this.timestamp = timestamp; + } + + /** + * @return timestamp2 + */ + public Calendar getTimestamp2() { + + return this.timestamp2; + } + + /** + * @param timestamp2 new value of {@link #gettimestamp2}. + */ + public void setTimestamp2(Calendar timestamp2) { + + this.timestamp2 = timestamp2; + } + + /** + * @return blob + */ + public byte[] getBlob() { + + return this.blob; + } + + /** + * @param blob new value of {@link #getblob}. + */ + public void setBlob(byte[] blob) { + + this.blob = blob; + } + + /** + * @return varchar2 + */ + public Class getVarchar2() { + + return this.varchar2; + } + + /** + * @param varchar2 new value of {@link #getvarchar2}. + */ + public void setVarchar2(Class varchar2) { + + this.varchar2 = varchar2; + } + + /** + * @return varchar3 + */ + public Locale getVarchar3() { + + return this.varchar3; + } + + /** + * @param varchar3 new value of {@link #getvarchar3}. + */ + public void setVarchar3(Locale varchar3) { + + this.varchar3 = varchar3; + } + + /** + * @return varchar4 + */ + public TimeZone getVarchar4() { + + return this.varchar4; + } + + /** + * @param varchar4 new value of {@link #getvarchar4}. + */ + public void setVarchar4(TimeZone varchar4) { + + this.varchar4 = varchar4; + } + + /** + * @return varchar5 + */ + public Currency getVarchar5() { + + return this.varchar5; + } + + /** + * @param varchar5 new value of {@link #getvarchar5}. + */ + public void setVarchar5(Currency varchar5) { + + this.varchar5 = varchar5; + } + +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java new file mode 100644 index 0000000000..b4c97bfeda --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestEntity.java @@ -0,0 +1,58 @@ +package com.devonfw.cobigen.templates.devon4j.test.templates.testclasses; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + * Test entity to test the correct generation of the enumerated type, the primary key, and name overriding + * + */ +@Entity +@Table(name = "SQLTEST") +public class SQLTestEntity { + @Id + @Column(name = "MY_ID_FIELD") + private Long id; + + @Column(name = "VALUENAME") + private Integer integerValue; + + @Enumerated(EnumType.STRING) + @Column(length = 420, name = "ENUM_TEST_FIELD_NAME_OVERRIDE") + private EnumForTest enumForTest; + + public Long getId() { + + return this.id; + } + + public void setId(Long id) { + + this.id = id; + } + + public Integer getIntegerValue() { + + return this.integerValue; + } + + public void setIntegerValue(Integer value) { + + this.integerValue = value; + } + + public EnumForTest getEnumForTest() { + + return this.enumForTest; + } + + public void setEnumForTest(EnumForTest enumForTest) { + + this.enumForTest = enumForTest; + } + +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestForeignKeysEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestForeignKeysEntity.java new file mode 100644 index 0000000000..b634e861b9 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestForeignKeysEntity.java @@ -0,0 +1,61 @@ +package com.devonfw.cobigen.templates.devon4j.test.templates.testclasses; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +/** + * Test entity to test the correct generation of the enumerated type, the primary key, and name overriding + * + */ +@Entity +@Table(name = "SQLTEST") +public class SQLTestForeignKeysEntity { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private Long id; + + @OneToOne() + @JoinColumn(referencedColumnName = "MY_ID_FIELD", name = "test_id") + private SQLTestEntity sqlTestEntity; + + /** + * @return id + */ + public Long getId() { + + return this.id; + } + + /** + * @param id new value of {@link #getid}. + */ + public void setId(Long id) { + + this.id = id; + } + + /** + * @return sqlTestEntity + */ + public SQLTestEntity getSqlTestEntity() { + + return this.sqlTestEntity; + } + + /** + * @param sqlTestEntity new value of {@link #getsqlTestEntity}. + */ + public void setSqlTestEntity(SQLTestEntity sqlTestEntity) { + + this.sqlTestEntity = sqlTestEntity; + } + +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestJoinTableEntity.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestJoinTableEntity.java new file mode 100644 index 0000000000..b28402fdac --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/templates/testclasses/SQLTestJoinTableEntity.java @@ -0,0 +1,35 @@ +package com.devonfw.cobigen.templates.devon4j.test.templates.testclasses; + +import javax.persistence.*; +import java.util.Set; + +@Entity +public class SQLTestJoinTableEntity { + @Id + @Column(name = "id", nullable = false) + private Long id; + + @ManyToMany + @JoinTable( + name = "MY_AWESOME_JOINTABLE", + joinColumns = @JoinColumn(name = "REF_ENTITY_ID", table = "REFERENCE", unique = true, referencedColumnName = "OVERRIDE_ID"), + inverseJoinColumns = @JoinColumn + ) + private Set referenceEntities; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Set getReferenceEntities() { + return referenceEntities; + } + + public void setReferenceEntities(Set referenceEntities) { + this.referenceEntities = referenceEntities; + } +} diff --git a/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java new file mode 100644 index 0000000000..408fd62239 --- /dev/null +++ b/cobigen-templates/templates-devon4j/src/test/java/com/devonfw/cobigen/templates/devon4j/test/utils/SQLUtilTest.java @@ -0,0 +1,27 @@ +package com.devonfw.cobigen.templates.devon4j.test.utils; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; + +import com.devonfw.cobigen.templates.devon4j.utils.SQLUtil; + +/** + * + * Test class for {@link SQLUtil} + * + */ +public class SQLUtilTest { + + /** + * Tests type mappings between simple Java types and corresponding SQL types + */ + @Test + public void testTypeMappings() { + assertThat(SQLUtil.mapType("Class")).isEqualTo("VARCHAR"); + assertThat(SQLUtil.mapType("byte[]")).isEqualTo("BLOB"); + assertThat(SQLUtil.mapType("Timestamp")).isEqualTo("TIMESTAMP"); + assertThat(SQLUtil.mapType("TimeZone")).isEqualTo("VARCHAR"); + assertThat(SQLUtil.mapType("Calendar")).isEqualTo("TIMESTAMP"); + } +} diff --git a/cobigen/cobigen-core/src/test/resources/testdata/unittest/ExternalProcess/DummyExe.exe b/cobigen/cobigen-core/src/test/resources/testdata/unittest/ExternalProcess/DummyExe.exe deleted file mode 100644 index 4612730a84..0000000000 Binary files a/cobigen/cobigen-core/src/test/resources/testdata/unittest/ExternalProcess/DummyExe.exe and /dev/null differ