Model generator
This commit is contained in:
46
.idea/compiler.xml
generated
46
.idea/compiler.xml
generated
@@ -61,52 +61,6 @@
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/org/antlr/antlr4-runtime/4.13.2/antlr4-runtime-4.13.2.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/net/bytebuddy/byte-buddy/1.17.8/byte-buddy-1.17.8.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/org/jboss/logging/jboss-logging/3.6.1.Final/jboss-logging-3.6.1.Final.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/org/projectlombok/lombok/1.18.42/lombok-1.18.42.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/org/mapstruct/mapstruct-processor/1.6.3/mapstruct-processor-1.6.3.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/com/google/errorprone/error_prone_core/2.37.0/error_prone_core-2.37.0.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/com/google/errorprone/error_prone_annotation/2.37.0/error_prone_annotation-2.37.0.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/com/google/errorprone/error_prone_check_api/2.37.0/error_prone_check_api-2.37.0.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/com/github/kevinstern/software-and-algorithms/1.0/software-and-algorithms-1.0.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/com/github/ben-manes/caffeine/caffeine/3.0.5/caffeine-3.0.5.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/com/google/googlejavaformat/google-java-format/1.24.0/google-java-format-1.24.0.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/org/pcollections/pcollections/4.0.1/pcollections-4.0.1.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/com/google/guava/guava/32.1.1-jre/guava-32.1.1-jre.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/org/checkerframework/checker-qual/3.33.0/checker-qual-3.33.0.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/com/google/j2objc/j2objc-annotations/2.8/j2objc-annotations-2.8.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/com/google/auto/auto-common/1.2.2/auto-common-1.2.2.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/io/github/eisop/dataflow-errorprone/3.41.0-eisop1/dataflow-errorprone-3.41.0-eisop1.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/com/google/auto/value/auto-value-annotations/1.9/auto-value-annotations-1.9.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/com/google/errorprone/error_prone_annotations/2.37.0/error_prone_annotations-2.37.0.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/com/google/protobuf/protobuf-java/3.25.5/protobuf-java-3.25.5.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/com/google/auto/service/auto-service-annotations/1.0.1/auto-service-annotations-1.0.1.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/javax/inject/javax.inject/1/javax.inject-1.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/com/uber/nullaway/nullaway/0.13.1/nullaway-0.13.1.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/org/checkerframework/dataflow-nullaway/3.53.0/dataflow-nullaway-3.53.0.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/org/hibernate/orm/hibernate-processor/7.2.4.Final/hibernate-processor-7.2.4.Final.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/org/hibernate/orm/hibernate-core/7.2.4.Final/hibernate-core-7.2.4.Final.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/jakarta/persistence/jakarta.persistence-api/3.2.0/jakarta.persistence-api-3.2.0.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/jakarta/transaction/jakarta.transaction-api/2.0.1/jakarta.transaction-api-2.0.1.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/com/fasterxml/classmate/1.7.1/classmate-1.7.1.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/jakarta/inject/jakarta.inject-api/2.0.1/jakarta.inject-api-2.0.1.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/org/hibernate/models/hibernate-models/1.0.1/hibernate-models-1.0.1.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/io/smallrye/jandex/3.3.2/jandex-3.3.2.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/jakarta/xml/bind/jakarta.xml.bind-api/4.0.4/jakarta.xml.bind-api-4.0.4.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/jakarta/activation/jakarta.activation-api/2.1.4/jakarta.activation-api-2.1.4.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/org/glassfish/jaxb/jaxb-runtime/4.0.6/jaxb-runtime-4.0.6.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/org/glassfish/jaxb/jaxb-core/4.0.6/jaxb-core-4.0.6.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/org/eclipse/angus/angus-activation/2.0.3/angus-activation-2.0.3.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/org/glassfish/jaxb/txw2/4.0.6/txw2-4.0.6.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/com/sun/istack/istack-commons-runtime/4.1.2/istack-commons-runtime-4.1.2.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/jakarta/validation/jakarta.validation-api/3.1.1/jakarta.validation-api-3.1.1.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/jakarta/annotation/jakarta.annotation-api/3.0.0/jakarta.annotation-api-3.0.0.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/org/antlr/antlr4-runtime/4.13.2/antlr4-runtime-4.13.2.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/net/bytebuddy/byte-buddy/1.17.8/byte-buddy-1.17.8.jar" />
|
||||
<entry name="$USER_HOME$/.m2/peak-repo/org/jboss/logging/jboss-logging/3.6.1.Final/jboss-logging-3.6.1.Final.jar" />
|
||||
</processorPath>
|
||||
<module name="peakup-backend" />
|
||||
</profile>
|
||||
|
||||
@@ -35,6 +35,11 @@
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>2.20.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup</groupId>
|
||||
<artifactId>javapoet</artifactId>
|
||||
<version>1.13.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.peakup.tools.generator;
|
||||
|
||||
import jakarta.annotation.Nullable;
|
||||
|
||||
public class ElementCollectionDefinition {
|
||||
|
||||
public String fieldName;
|
||||
public String collectionTable;
|
||||
public String joinColumn;
|
||||
/** Simple type name: "String" for scalar collections, or an embeddable class name. */
|
||||
public String valueType;
|
||||
/** Column name for scalar (String) collections; null for embeddable collections. */
|
||||
public @Nullable String valueColumn;
|
||||
public @Nullable String orderColumn;
|
||||
/** Inline embeddable definition — generated alongside the entity when present. */
|
||||
public @Nullable EmbeddableDefinition embeddable;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.peakup.tools.generator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class EmbeddableDefinition {
|
||||
|
||||
public String className;
|
||||
public List<FieldDefinition> fields = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.peakup.tools.generator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class EnumDefinition {
|
||||
|
||||
public String className;
|
||||
public List<EnumValueDefinition> values = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.peakup.tools.generator;
|
||||
|
||||
public class EnumValueDefinition {
|
||||
|
||||
public String name;
|
||||
public int key;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.peakup.tools.generator;
|
||||
|
||||
import jakarta.annotation.Nullable;
|
||||
|
||||
public class FieldDefinition {
|
||||
|
||||
public String name;
|
||||
public String javaType;
|
||||
public boolean nullable;
|
||||
public String columnName;
|
||||
public @Nullable String columnDefinition;
|
||||
public @Nullable String jdbcTypeCode;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.peakup.tools.generator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ModelDefinition {
|
||||
|
||||
public String modelName;
|
||||
public String packageBase;
|
||||
public String tableName;
|
||||
/** "AbstractEntity" or "AbstractTimestampedEntity" */
|
||||
public String baseClass;
|
||||
public String apiPath;
|
||||
/** When true, adds @JsonInclude(JsonInclude.Include.NON_NULL) to the entity. */
|
||||
public boolean jsonIncludeNonNull;
|
||||
public List<FieldDefinition> fields = new ArrayList<>();
|
||||
public List<ElementCollectionDefinition> elementCollections = new ArrayList<>();
|
||||
public List<OneToManyDefinition> oneToManyRelations = new ArrayList<>();
|
||||
public List<EnumDefinition> enums = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,659 @@
|
||||
package com.peakup.tools.generator;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.squareup.javapoet.AnnotationSpec;
|
||||
import com.squareup.javapoet.ArrayTypeName;
|
||||
import com.squareup.javapoet.ClassName;
|
||||
import com.squareup.javapoet.FieldSpec;
|
||||
import com.squareup.javapoet.JavaFile;
|
||||
import com.squareup.javapoet.MethodSpec;
|
||||
import com.squareup.javapoet.ParameterSpec;
|
||||
import com.squareup.javapoet.ParameterizedTypeName;
|
||||
import com.squareup.javapoet.TypeName;
|
||||
import com.squareup.javapoet.TypeSpec;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Generates a full domain slice (model, DAO, service, resource) from a JSON definition file.
|
||||
*
|
||||
* <p>Run from project root:
|
||||
* <pre>
|
||||
* JAVA_HOME=/home/ingo/.jdks/temurin-21.0.7 mvn -f peakup-tools/pom.xml \
|
||||
* -s /home/ingo/.m2/settings_peak.xml -Dmaven.repo.local=/home/ingo/.m2/peak-repo \
|
||||
* compile exec:java -Dexec.mainClass=com.peakup.tools.generator.ModelGeneratorTool \
|
||||
* "-Dexec.args=/path/to/definition.json [outputDir]"
|
||||
* </pre>
|
||||
*
|
||||
* <p>Default outputDir: peakup-backend/src/main/java (relative to working directory).
|
||||
*/
|
||||
public class ModelGeneratorTool {
|
||||
|
||||
private static final String COMMON_PKG = "com.peakup.common";
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Entry point
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
public static void main(final String[] args) throws Exception {
|
||||
if (args.length < 1) {
|
||||
System.err.println("Usage: ModelGeneratorTool <definition.json> [outputDir]");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
final Path definitionPath = Paths.get(args[0]);
|
||||
final Path outputDir = args.length >= 2
|
||||
? Paths.get(args[1])
|
||||
: Paths.get(System.getProperty("user.dir")).resolve("peakup-backend/src/main/java");
|
||||
|
||||
final ModelDefinition def = new ObjectMapper().readValue(definitionPath.toFile(), ModelDefinition.class);
|
||||
|
||||
System.out.println("Generating: " + def.modelName + " → " + outputDir.toAbsolutePath());
|
||||
|
||||
final ModelGeneratorTool tool = new ModelGeneratorTool();
|
||||
final Set<String> enumNames = def.enums.stream().map(e -> e.className).collect(Collectors.toSet());
|
||||
|
||||
for (final EnumDefinition enumDef : def.enums) {
|
||||
write(tool.generateEnum(enumDef, def.packageBase + ".model"), outputDir, "model/" + enumDef.className);
|
||||
}
|
||||
for (final ElementCollectionDefinition ec : def.elementCollections) {
|
||||
if (ec.embeddable != null) {
|
||||
write(tool.generateEmbeddable(ec.embeddable, def.packageBase + ".model"), outputDir, "model/" + ec.embeddable.className);
|
||||
}
|
||||
}
|
||||
write(tool.generateModel(def, enumNames), outputDir, "model/" + def.modelName);
|
||||
write(tool.generateDAO(def), outputDir, "dao/" + def.modelName + "DAO");
|
||||
write(tool.generateService(def), outputDir, "service/" + def.modelName + "Service");
|
||||
write(tool.generateResource(def), outputDir, def.modelName + "Resource");
|
||||
|
||||
System.out.println("Done.");
|
||||
}
|
||||
|
||||
private static void write(final JavaFile file, final Path outputDir, final String label) throws Exception {
|
||||
file.writeTo(outputDir);
|
||||
System.out.println(" " + label + ".java");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Enum
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private JavaFile generateEnum(final EnumDefinition enumDef, final String modelPackage) {
|
||||
final ClassName selfClass = ClassName.get(modelPackage, enumDef.className);
|
||||
final ClassName persistableEnum = ClassName.get(COMMON_PKG + ".model", "PersistableEnum");
|
||||
final ClassName abstractConverter = ClassName.get(COMMON_PKG + ".model", "AbstractEnumConverter");
|
||||
|
||||
final TypeSpec.Builder enumBuilder = TypeSpec.enumBuilder(enumDef.className)
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addSuperinterface(ParameterizedTypeName.get(persistableEnum, selfClass));
|
||||
|
||||
for (final EnumValueDefinition value : enumDef.values) {
|
||||
enumBuilder.addEnumConstant(value.name, TypeSpec.anonymousClassBuilder("$L", value.key).build());
|
||||
}
|
||||
|
||||
enumBuilder.addField(FieldSpec.builder(TypeName.INT, "key", Modifier.PRIVATE, Modifier.FINAL).build());
|
||||
|
||||
enumBuilder.addMethod(MethodSpec.constructorBuilder()
|
||||
.addParameter(TypeName.INT, "key")
|
||||
.addStatement("this.key = key")
|
||||
.build());
|
||||
|
||||
enumBuilder.addMethod(MethodSpec.methodBuilder("getKey")
|
||||
.addAnnotation(Override.class)
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.returns(ClassName.get(Integer.class))
|
||||
.addStatement("return key")
|
||||
.build());
|
||||
|
||||
final TypeSpec converterClass = TypeSpec.classBuilder("Converter")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addAnnotation(AnnotationSpec.builder(ClassName.get("jakarta.persistence", "Converter"))
|
||||
.addMember("autoApply", "$L", true)
|
||||
.build())
|
||||
.superclass(ParameterizedTypeName.get(abstractConverter, selfClass))
|
||||
.addMethod(MethodSpec.methodBuilder("getEnumByKey")
|
||||
.addAnnotation(Override.class)
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.returns(selfClass)
|
||||
.addParameter(ParameterSpec.builder(ClassName.get(Integer.class), "key").addModifiers(Modifier.FINAL).build())
|
||||
.addStatement("return $T.getEnumByKey(key, $T.values())", persistableEnum, selfClass)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
enumBuilder.addType(converterClass);
|
||||
|
||||
return JavaFile.builder(modelPackage, enumBuilder.build()).skipJavaLangImports(true).build();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Embeddable
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private JavaFile generateEmbeddable(final EmbeddableDefinition embeddable, final String modelPackage) {
|
||||
final TypeSpec.Builder classBuilder = TypeSpec.classBuilder(embeddable.className)
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(ClassName.get("jakarta.persistence", "Embeddable"))
|
||||
.addAnnotation(ClassName.get("lombok", "Data"));
|
||||
|
||||
for (final FieldDefinition field : embeddable.fields) {
|
||||
classBuilder.addField(buildColumnField(field, modelPackage, Set.of()));
|
||||
}
|
||||
|
||||
classBuilder.addMethod(buildHibernateConstructor());
|
||||
|
||||
return JavaFile.builder(modelPackage, classBuilder.build()).skipJavaLangImports(true).build();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Entity model
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private JavaFile generateModel(final ModelDefinition def, final Set<String> enumNames) {
|
||||
final String modelPackage = def.packageBase + ".model";
|
||||
final ClassName modelClass = ClassName.get(modelPackage, def.modelName);
|
||||
|
||||
final TypeSpec.Builder classBuilder = TypeSpec.classBuilder(def.modelName)
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(ClassName.get("jakarta.persistence", "Entity"))
|
||||
.addAnnotation(AnnotationSpec.builder(ClassName.get("jakarta.persistence", "Table"))
|
||||
.addMember("name", "$S", def.tableName)
|
||||
.build())
|
||||
.addAnnotation(ClassName.get("lombok", "Data"))
|
||||
.addAnnotation(AnnotationSpec.builder(ClassName.get("lombok", "EqualsAndHashCode"))
|
||||
.addMember("callSuper", "$L", true)
|
||||
.build())
|
||||
.superclass(ClassName.get(COMMON_PKG + ".model", def.baseClass));
|
||||
|
||||
if (def.jsonIncludeNonNull) {
|
||||
classBuilder.addAnnotation(AnnotationSpec.builder(
|
||||
ClassName.get("com.fasterxml.jackson.annotation", "JsonInclude"))
|
||||
.addMember("value", "$T.NON_NULL",
|
||||
ClassName.get("com.fasterxml.jackson.annotation", "JsonInclude", "Include"))
|
||||
.build());
|
||||
}
|
||||
|
||||
for (final FieldDefinition field : def.fields) {
|
||||
classBuilder.addField(buildColumnField(field, modelPackage, enumNames));
|
||||
}
|
||||
addElementCollectionFields(classBuilder, def, modelPackage);
|
||||
addOneToManyFields(classBuilder, def, modelPackage);
|
||||
classBuilder.addMethod(buildMergeFromMethod(def, modelClass));
|
||||
classBuilder.addMethod(buildHibernateConstructor());
|
||||
|
||||
return JavaFile.builder(modelPackage, classBuilder.build()).skipJavaLangImports(true).build();
|
||||
}
|
||||
|
||||
private void addElementCollectionFields(final TypeSpec.Builder classBuilder,
|
||||
final ModelDefinition def, final String modelPackage) {
|
||||
for (final ElementCollectionDefinition ec : def.elementCollections) {
|
||||
final TypeName elementType = "String".equals(ec.valueType)
|
||||
? ClassName.get(String.class)
|
||||
: ClassName.get(modelPackage, ec.valueType);
|
||||
|
||||
final FieldSpec.Builder fieldBuilder = FieldSpec.builder(
|
||||
ParameterizedTypeName.get(ClassName.get(List.class), elementType),
|
||||
ec.fieldName, Modifier.PUBLIC)
|
||||
.addAnnotation(ClassName.get("com.fasterxml.jackson.annotation", "JsonIgnore"))
|
||||
.addAnnotation(AnnotationSpec.builder(ClassName.get("jakarta.persistence", "ElementCollection"))
|
||||
.addMember("fetch", "$T.LAZY", ClassName.get("jakarta.persistence", "FetchType"))
|
||||
.build())
|
||||
.addAnnotation(AnnotationSpec.builder(ClassName.get("jakarta.persistence", "CollectionTable"))
|
||||
.addMember("name", "$S", ec.collectionTable)
|
||||
.addMember("joinColumns", "@$T(name = $S)",
|
||||
ClassName.get("jakarta.persistence", "JoinColumn"), ec.joinColumn)
|
||||
.build())
|
||||
.initializer("new $T<>()", ArrayList.class);
|
||||
|
||||
if (ec.orderColumn != null) {
|
||||
fieldBuilder.addAnnotation(AnnotationSpec.builder(ClassName.get("jakarta.persistence", "OrderColumn"))
|
||||
.addMember("name", "$S", ec.orderColumn)
|
||||
.build());
|
||||
}
|
||||
if (ec.valueColumn != null) {
|
||||
fieldBuilder.addAnnotation(AnnotationSpec.builder(ClassName.get("jakarta.persistence", "Column"))
|
||||
.addMember("name", "$S", ec.valueColumn)
|
||||
.build());
|
||||
}
|
||||
classBuilder.addField(fieldBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
private void addOneToManyFields(final TypeSpec.Builder classBuilder,
|
||||
final ModelDefinition def, final String modelPackage) {
|
||||
for (final OneToManyDefinition otm : def.oneToManyRelations) {
|
||||
final ClassName entityClass = resolveClassName(otm.entityClass, modelPackage);
|
||||
|
||||
final FieldSpec.Builder fieldBuilder = FieldSpec.builder(
|
||||
ParameterizedTypeName.get(ClassName.get(List.class), entityClass),
|
||||
otm.fieldName, Modifier.PUBLIC)
|
||||
.addAnnotation(AnnotationSpec.builder(ClassName.get("jakarta.persistence", "OneToMany"))
|
||||
.addMember("fetch", "$T.EAGER", ClassName.get("jakarta.persistence", "FetchType"))
|
||||
.build())
|
||||
.addAnnotation(AnnotationSpec.builder(ClassName.get("jakarta.persistence", "JoinColumn"))
|
||||
.addMember("name", "$S", otm.joinColumn)
|
||||
.build())
|
||||
.addAnnotation(AnnotationSpec.builder(ClassName.get("jakarta.persistence", "OrderBy"))
|
||||
.addMember("value", "$S", otm.orderBy)
|
||||
.build())
|
||||
.initializer("new $T<>()", ArrayList.class);
|
||||
|
||||
if (otm.fetchMode != null) {
|
||||
fieldBuilder.addAnnotation(AnnotationSpec.builder(ClassName.get("org.hibernate.annotations", "Fetch"))
|
||||
.addMember("value", "$T.$L",
|
||||
ClassName.get("org.hibernate.annotations", "FetchMode"), otm.fetchMode)
|
||||
.build());
|
||||
}
|
||||
classBuilder.addField(fieldBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
private MethodSpec buildMergeFromMethod(final ModelDefinition def, final ClassName modelClass) {
|
||||
final MethodSpec.Builder builder = MethodSpec.methodBuilder("mergeFrom")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.returns(TypeName.VOID)
|
||||
.addParameter(ParameterSpec.builder(modelClass, "source").addModifiers(Modifier.FINAL).build());
|
||||
for (final FieldDefinition field : def.fields) {
|
||||
builder.addStatement("this.$L = source.$L", field.name, field.name);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// DAO
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private JavaFile generateDAO(final ModelDefinition def) {
|
||||
final String modelPackage = def.packageBase + ".model";
|
||||
final String daoPackage = def.packageBase + ".dao";
|
||||
final ClassName modelClass = ClassName.get(modelPackage, def.modelName);
|
||||
|
||||
final TypeSpec.Builder classBuilder = TypeSpec.classBuilder(def.modelName + "DAO")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(ClassName.get("jakarta.enterprise.context", "ApplicationScoped"))
|
||||
.superclass(ParameterizedTypeName.get(
|
||||
ClassName.get(COMMON_PKG + ".dao", "AbstractDAO"), modelClass));
|
||||
|
||||
classBuilder.addMethod(MethodSpec.methodBuilder("getEntityClass")
|
||||
.addAnnotation(Override.class)
|
||||
.addModifiers(Modifier.PROTECTED)
|
||||
.returns(ParameterizedTypeName.get(ClassName.get(Class.class), modelClass))
|
||||
.addStatement("return $T.class", modelClass)
|
||||
.build());
|
||||
|
||||
if (hasAthleteId(def)) {
|
||||
classBuilder.addMethod(buildGetByAthleteIdDAO(modelClass));
|
||||
}
|
||||
|
||||
return JavaFile.builder(daoPackage, classBuilder.build()).skipJavaLangImports(true).build();
|
||||
}
|
||||
|
||||
private MethodSpec buildGetByAthleteIdDAO(final ClassName modelClass) {
|
||||
return MethodSpec.methodBuilder("getByAthleteId")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.returns(ParameterizedTypeName.get(ClassName.get(List.class), modelClass))
|
||||
.addParameter(ParameterSpec.builder(TypeName.LONG, "athleteId").addModifiers(Modifier.FINAL).build())
|
||||
.addStatement("final $T cb = em.getCriteriaBuilder()",
|
||||
ClassName.get("jakarta.persistence.criteria", "CriteriaBuilder"))
|
||||
.addStatement("final $T<$T> cq = cb.createQuery($T.class)",
|
||||
ClassName.get("jakarta.persistence.criteria", "CriteriaQuery"), modelClass, modelClass)
|
||||
.addStatement("final $T<$T> root = cq.from($T.class)",
|
||||
ClassName.get("jakarta.persistence.criteria", "Root"), modelClass, modelClass)
|
||||
.addStatement("cq.select(root).where(cb.equal(root.get($S), athleteId))", "athleteId")
|
||||
.addStatement("return em.createQuery(cq).getResultList()")
|
||||
.build();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Service
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private JavaFile generateService(final ModelDefinition def) {
|
||||
final String modelPackage = def.packageBase + ".model";
|
||||
final String daoPackage = def.packageBase + ".dao";
|
||||
final String servicePackage = def.packageBase + ".service";
|
||||
final ClassName modelClass = ClassName.get(modelPackage, def.modelName);
|
||||
final ClassName daoClass = ClassName.get(daoPackage, def.modelName + "DAO");
|
||||
final ClassName transactional = ClassName.get("jakarta.transaction", "Transactional");
|
||||
final String daoFieldName = toLowerFirst(def.modelName) + "DAO";
|
||||
final String entityParam = toLowerFirst(def.modelName);
|
||||
|
||||
final TypeSpec.Builder classBuilder = TypeSpec.classBuilder(def.modelName + "Service")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(ClassName.get("jakarta.enterprise.context", "ApplicationScoped"))
|
||||
.superclass(ParameterizedTypeName.get(
|
||||
ClassName.get(COMMON_PKG + ".service", "AbstractService"), modelClass, daoClass));
|
||||
|
||||
classBuilder.addField(FieldSpec.builder(daoClass, daoFieldName)
|
||||
.addAnnotation(ClassName.get("jakarta.inject", "Inject"))
|
||||
.addAnnotation(suppressNullAwayInit())
|
||||
.build());
|
||||
|
||||
classBuilder.addMethod(MethodSpec.methodBuilder("getDAO")
|
||||
.addAnnotation(Override.class)
|
||||
.addModifiers(Modifier.PROTECTED)
|
||||
.returns(daoClass)
|
||||
.addStatement("return $L", daoFieldName)
|
||||
.build());
|
||||
|
||||
classBuilder.addMethod(MethodSpec.methodBuilder("getById")
|
||||
.addAnnotation(Override.class)
|
||||
.addAnnotation(transactional)
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.returns(modelClass)
|
||||
.addParameter(ParameterSpec.builder(TypeName.LONG, "id").addModifiers(Modifier.FINAL).build())
|
||||
.addStatement("return super.getById(id)")
|
||||
.build());
|
||||
|
||||
classBuilder.addMethod(MethodSpec.methodBuilder("getAll")
|
||||
.addAnnotation(Override.class)
|
||||
.addAnnotation(transactional)
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.returns(ParameterizedTypeName.get(ClassName.get(List.class), modelClass))
|
||||
.addStatement("return super.getAll()")
|
||||
.build());
|
||||
|
||||
classBuilder.addMethod(MethodSpec.methodBuilder("insert")
|
||||
.addAnnotation(Override.class)
|
||||
.addAnnotation(transactional)
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addParameter(ParameterSpec.builder(modelClass, entityParam).addModifiers(Modifier.FINAL).build())
|
||||
.returns(TypeName.VOID)
|
||||
.addStatement("super.insert($L)", entityParam)
|
||||
.build());
|
||||
|
||||
classBuilder.addMethod(MethodSpec.methodBuilder("update")
|
||||
.addAnnotation(Override.class)
|
||||
.addAnnotation(transactional)
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addParameter(ParameterSpec.builder(modelClass, entityParam).addModifiers(Modifier.FINAL).build())
|
||||
.returns(TypeName.VOID)
|
||||
.addStatement("super.update($L)", entityParam)
|
||||
.build());
|
||||
|
||||
classBuilder.addMethod(MethodSpec.methodBuilder("delete")
|
||||
.addAnnotation(Override.class)
|
||||
.addAnnotation(transactional)
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addParameter(ParameterSpec.builder(modelClass, entityParam).addModifiers(Modifier.FINAL).build())
|
||||
.returns(TypeName.VOID)
|
||||
.addStatement("super.delete($L)", entityParam)
|
||||
.build());
|
||||
|
||||
if (hasAthleteId(def)) {
|
||||
classBuilder.addMethod(MethodSpec.methodBuilder("getByAthleteId")
|
||||
.addAnnotation(transactional)
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.returns(ParameterizedTypeName.get(ClassName.get(List.class), modelClass))
|
||||
.addParameter(ParameterSpec.builder(TypeName.LONG, "athleteId").addModifiers(Modifier.FINAL).build())
|
||||
.addStatement("return $L.getByAthleteId(athleteId)", daoFieldName)
|
||||
.build());
|
||||
}
|
||||
|
||||
return JavaFile.builder(servicePackage, classBuilder.build()).skipJavaLangImports(true).build();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Resource
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private JavaFile generateResource(final ModelDefinition def) {
|
||||
final String modelPackage = def.packageBase + ".model";
|
||||
final String servicePackage = def.packageBase + ".service";
|
||||
final ClassName modelClass = ClassName.get(modelPackage, def.modelName);
|
||||
final ClassName serviceClass = ClassName.get(servicePackage, def.modelName + "Service");
|
||||
final ClassName restResponse = ClassName.get("org.jboss.resteasy.reactive", "RestResponse");
|
||||
final ClassName noResultException = ClassName.get("jakarta.persistence", "NoResultException");
|
||||
final ClassName response = ClassName.get("jakarta.ws.rs.core", "Response");
|
||||
final ClassName mediaType = ClassName.get("jakarta.ws.rs.core", "MediaType");
|
||||
final String serviceFieldName = toLowerFirst(def.modelName) + "Service";
|
||||
final String entityParam = toLowerFirst(def.modelName);
|
||||
|
||||
final TypeSpec.Builder classBuilder = TypeSpec.classBuilder(def.modelName + "Resource")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(AnnotationSpec.builder(ClassName.get("jakarta.ws.rs", "Path"))
|
||||
.addMember("value", "$S", def.apiPath)
|
||||
.build())
|
||||
.addAnnotation(ClassName.get("jakarta.enterprise.context", "ApplicationScoped"))
|
||||
.addAnnotation(AnnotationSpec.builder(ClassName.get("jakarta.ws.rs", "Produces"))
|
||||
.addMember("value", "$T.APPLICATION_JSON", mediaType)
|
||||
.build())
|
||||
.addAnnotation(AnnotationSpec.builder(ClassName.get("jakarta.ws.rs", "Consumes"))
|
||||
.addMember("value", "$T.APPLICATION_JSON", mediaType)
|
||||
.build())
|
||||
.superclass(ClassName.get(COMMON_PKG, "AbstractResource"));
|
||||
|
||||
classBuilder.addField(FieldSpec.builder(serviceClass, serviceFieldName)
|
||||
.addAnnotation(ClassName.get("jakarta.inject", "Inject"))
|
||||
.addAnnotation(suppressNullAwayInit())
|
||||
.build());
|
||||
|
||||
addGetEndpoint(classBuilder, def, modelClass, serviceClass, serviceFieldName, restResponse);
|
||||
|
||||
classBuilder.addMethod(buildGetByIdEndpoint(modelClass, serviceFieldName, restResponse, noResultException));
|
||||
classBuilder.addMethod(buildCreateEndpoint(modelClass, serviceFieldName, entityParam, restResponse, response));
|
||||
classBuilder.addMethod(buildUpdateEndpoint(modelClass, serviceFieldName, entityParam, restResponse, noResultException));
|
||||
classBuilder.addMethod(buildDeleteEndpoint(modelClass, serviceFieldName, restResponse, noResultException));
|
||||
|
||||
return JavaFile.builder(def.packageBase, classBuilder.build()).skipJavaLangImports(true).build();
|
||||
}
|
||||
|
||||
private void addGetEndpoint(final TypeSpec.Builder classBuilder, final ModelDefinition def,
|
||||
final ClassName modelClass, final ClassName serviceClass,
|
||||
final String serviceFieldName, final ClassName restResponse) {
|
||||
final TypeName listResponse = ParameterizedTypeName.get(restResponse,
|
||||
ParameterizedTypeName.get(ClassName.get(List.class), modelClass));
|
||||
|
||||
if (hasAthleteId(def)) {
|
||||
classBuilder.addMethod(MethodSpec.methodBuilder("getByAthlete")
|
||||
.addAnnotation(ClassName.get("jakarta.ws.rs", "GET"))
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.returns(listResponse)
|
||||
.addParameter(ParameterSpec.builder(ClassName.get(Long.class), "athleteId")
|
||||
.addAnnotation(AnnotationSpec.builder(ClassName.get("jakarta.ws.rs", "QueryParam"))
|
||||
.addMember("value", "$S", "athleteId").build())
|
||||
.addAnnotation(ClassName.get("jakarta.annotation", "Nullable"))
|
||||
.addModifiers(Modifier.FINAL)
|
||||
.build())
|
||||
.beginControlFlow("if (athleteId == null)")
|
||||
.addStatement("throw new $T($S)",
|
||||
ClassName.get("jakarta.ws.rs", "BadRequestException"),
|
||||
"athleteId query parameter is required")
|
||||
.endControlFlow()
|
||||
.addStatement("return $T.ok($L.getByAthleteId(athleteId))", restResponse, serviceFieldName)
|
||||
.build());
|
||||
} else {
|
||||
classBuilder.addMethod(MethodSpec.methodBuilder("getAll")
|
||||
.addAnnotation(ClassName.get("jakarta.ws.rs", "GET"))
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.returns(listResponse)
|
||||
.addStatement("return $T.ok($L.getAll())", restResponse, serviceFieldName)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
private MethodSpec buildGetByIdEndpoint(final ClassName modelClass, final String serviceFieldName,
|
||||
final ClassName restResponse, final ClassName noResultException) {
|
||||
return MethodSpec.methodBuilder("getById")
|
||||
.addAnnotation(ClassName.get("jakarta.ws.rs", "GET"))
|
||||
.addAnnotation(AnnotationSpec.builder(ClassName.get("jakarta.ws.rs", "Path"))
|
||||
.addMember("value", "$S", "/{id}").build())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.returns(ParameterizedTypeName.get(restResponse, modelClass))
|
||||
.addParameter(buildIdPathParam())
|
||||
.beginControlFlow("try")
|
||||
.addStatement("return $T.ok($L.getById(id))", restResponse, serviceFieldName)
|
||||
.nextControlFlow("catch (final $T e)", noResultException)
|
||||
.addStatement("return $T.notFound()", restResponse)
|
||||
.endControlFlow()
|
||||
.build();
|
||||
}
|
||||
|
||||
private MethodSpec buildCreateEndpoint(final ClassName modelClass, final String serviceFieldName,
|
||||
final String entityParam, final ClassName restResponse,
|
||||
final ClassName response) {
|
||||
return MethodSpec.methodBuilder("create")
|
||||
.addAnnotation(ClassName.get("jakarta.ws.rs", "POST"))
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.returns(ParameterizedTypeName.get(restResponse, modelClass))
|
||||
.addParameter(ParameterSpec.builder(modelClass, entityParam).addModifiers(Modifier.FINAL).build())
|
||||
.addStatement("$L.insert($L)", serviceFieldName, entityParam)
|
||||
.addStatement("return $T.status($T.Status.CREATED, $L)", restResponse, response, entityParam)
|
||||
.build();
|
||||
}
|
||||
|
||||
private MethodSpec buildUpdateEndpoint(final ClassName modelClass, final String serviceFieldName,
|
||||
final String entityParam, final ClassName restResponse,
|
||||
final ClassName noResultException) {
|
||||
return MethodSpec.methodBuilder("update")
|
||||
.addAnnotation(ClassName.get("jakarta.ws.rs", "PUT"))
|
||||
.addAnnotation(AnnotationSpec.builder(ClassName.get("jakarta.ws.rs", "Path"))
|
||||
.addMember("value", "$S", "/{id}").build())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.returns(ParameterizedTypeName.get(restResponse, modelClass))
|
||||
.addParameter(buildIdPathParam())
|
||||
.addParameter(ParameterSpec.builder(modelClass, entityParam).addModifiers(Modifier.FINAL).build())
|
||||
.beginControlFlow("try")
|
||||
.addStatement("final $T existing = $L.getById(id)", modelClass, serviceFieldName)
|
||||
.addStatement("existing.mergeFrom($L)", entityParam)
|
||||
.addStatement("$L.update(existing)", serviceFieldName)
|
||||
.addStatement("return $T.ok(existing)", restResponse)
|
||||
.nextControlFlow("catch (final $T e)", noResultException)
|
||||
.addStatement("return $T.notFound()", restResponse)
|
||||
.endControlFlow()
|
||||
.build();
|
||||
}
|
||||
|
||||
private MethodSpec buildDeleteEndpoint(final ClassName modelClass, final String serviceFieldName,
|
||||
final ClassName restResponse, final ClassName noResultException) {
|
||||
return MethodSpec.methodBuilder("delete")
|
||||
.addAnnotation(ClassName.get("jakarta.ws.rs", "DELETE"))
|
||||
.addAnnotation(AnnotationSpec.builder(ClassName.get("jakarta.ws.rs", "Path"))
|
||||
.addMember("value", "$S", "/{id}").build())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.returns(ParameterizedTypeName.get(restResponse, ClassName.get(Void.class)))
|
||||
.addParameter(buildIdPathParam())
|
||||
.beginControlFlow("try")
|
||||
.addStatement("final $T existing = $L.getById(id)", modelClass, serviceFieldName)
|
||||
.addStatement("$L.delete(existing)", serviceFieldName)
|
||||
.addStatement("return $T.noContent()", restResponse)
|
||||
.nextControlFlow("catch (final $T e)", noResultException)
|
||||
.addStatement("return $T.notFound()", restResponse)
|
||||
.endControlFlow()
|
||||
.build();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Shared field builders
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private FieldSpec buildColumnField(final FieldDefinition field, final String modelPackage,
|
||||
final Set<String> enumNames) {
|
||||
final TypeName typeName = resolveTypeName(field.javaType, modelPackage, enumNames);
|
||||
|
||||
final AnnotationSpec.Builder colAnn = AnnotationSpec.builder(ClassName.get("jakarta.persistence", "Column"))
|
||||
.addMember("name", "$S", field.columnName);
|
||||
if (!field.nullable && !isPrimitive(field.javaType)) {
|
||||
colAnn.addMember("nullable", "$L", false);
|
||||
}
|
||||
if (field.columnDefinition != null) {
|
||||
colAnn.addMember("columnDefinition", "$S", field.columnDefinition);
|
||||
}
|
||||
|
||||
final FieldSpec.Builder fieldBuilder = FieldSpec.builder(typeName, field.name, Modifier.PUBLIC)
|
||||
.addAnnotation(colAnn.build());
|
||||
|
||||
if (field.nullable) {
|
||||
fieldBuilder.addAnnotation(ClassName.get("jakarta.annotation", "Nullable"));
|
||||
}
|
||||
if ("ARRAY".equals(field.jdbcTypeCode)) {
|
||||
fieldBuilder.addAnnotation(AnnotationSpec.builder(ClassName.get("org.hibernate.annotations", "JdbcTypeCode"))
|
||||
.addMember("value", "$T.ARRAY", ClassName.get("org.hibernate.type", "SqlTypes"))
|
||||
.build());
|
||||
}
|
||||
return fieldBuilder.build();
|
||||
}
|
||||
|
||||
private MethodSpec buildHibernateConstructor() {
|
||||
return MethodSpec.constructorBuilder()
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
|
||||
.addMember("value", "$S", "NullAway.Init")
|
||||
.build())
|
||||
.addComment("hibernate")
|
||||
.build();
|
||||
}
|
||||
|
||||
private ParameterSpec buildIdPathParam() {
|
||||
return ParameterSpec.builder(TypeName.LONG, "id")
|
||||
.addAnnotation(AnnotationSpec.builder(ClassName.get("jakarta.ws.rs", "PathParam"))
|
||||
.addMember("value", "$S", "id").build())
|
||||
.addModifiers(Modifier.FINAL)
|
||||
.build();
|
||||
}
|
||||
|
||||
private AnnotationSpec suppressNullAwayInit() {
|
||||
return AnnotationSpec.builder(SuppressWarnings.class)
|
||||
.addMember("value", "$S", "NullAway.Init")
|
||||
.build();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private static TypeName resolveTypeName(final String javaType, final String modelPackage,
|
||||
final Set<String> localTypes) {
|
||||
return switch (javaType) {
|
||||
case "int" -> TypeName.INT;
|
||||
case "long" -> TypeName.LONG;
|
||||
case "double" -> TypeName.DOUBLE;
|
||||
case "float" -> TypeName.FLOAT;
|
||||
case "boolean" -> TypeName.BOOLEAN;
|
||||
case "Integer" -> ClassName.get(Integer.class);
|
||||
case "Long" -> ClassName.get(Long.class);
|
||||
case "Double" -> ClassName.get(Double.class);
|
||||
case "Float" -> ClassName.get(Float.class);
|
||||
case "Boolean" -> ClassName.get(Boolean.class);
|
||||
case "String" -> ClassName.get(String.class);
|
||||
case "int[]" -> ArrayTypeName.of(TypeName.INT);
|
||||
case "long[]" -> ArrayTypeName.of(TypeName.LONG);
|
||||
case "double[]" -> ArrayTypeName.of(TypeName.DOUBLE);
|
||||
case "String[]" -> ArrayTypeName.of(ClassName.get(String.class));
|
||||
case "Instant" -> ClassName.get("java.time", "Instant");
|
||||
case "LocalDate" -> ClassName.get("java.time", "LocalDate");
|
||||
default -> resolveClassName(javaType, modelPackage);
|
||||
};
|
||||
}
|
||||
|
||||
private static ClassName resolveClassName(final String className, final String modelPackage) {
|
||||
if (className.contains(".")) {
|
||||
final int lastDot = className.lastIndexOf('.');
|
||||
return ClassName.get(className.substring(0, lastDot), className.substring(lastDot + 1));
|
||||
}
|
||||
return ClassName.get(modelPackage, className);
|
||||
}
|
||||
|
||||
private static boolean isPrimitive(final String javaType) {
|
||||
return switch (javaType) {
|
||||
case "int", "long", "double", "float", "boolean" -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean hasAthleteId(final ModelDefinition def) {
|
||||
return def.fields.stream().anyMatch(f -> "athleteId".equals(f.name));
|
||||
}
|
||||
|
||||
private static String toLowerFirst(final String name) {
|
||||
if (name == null || name.isEmpty()) {
|
||||
return name;
|
||||
}
|
||||
return Character.toLowerCase(name.charAt(0)) + name.substring(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.peakup.tools.generator;
|
||||
|
||||
import jakarta.annotation.Nullable;
|
||||
|
||||
public class OneToManyDefinition {
|
||||
|
||||
public String fieldName;
|
||||
/**
|
||||
* Simple or fully-qualified class name of the target entity.
|
||||
* Simple names are resolved against the model package; FQNs are used as-is.
|
||||
*/
|
||||
public String entityClass;
|
||||
public String joinColumn;
|
||||
public String orderBy;
|
||||
/** Hibernate FetchMode constant name, e.g. "SUBSELECT". Null omits @Fetch. */
|
||||
public @Nullable String fetchMode;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"modelName": "TrainingNote",
|
||||
"packageBase": "com.peakup.trainingnote",
|
||||
"tableName": "training_note",
|
||||
"baseClass": "AbstractTimestampedEntity",
|
||||
"apiPath": "/api/training-notes",
|
||||
"jsonIncludeNonNull": false,
|
||||
"fields": [
|
||||
{ "name": "athleteId", "javaType": "Long", "nullable": false, "columnName": "athlete_id" },
|
||||
{ "name": "title", "javaType": "String", "nullable": false, "columnName": "title" },
|
||||
{ "name": "body", "javaType": "String", "nullable": true, "columnName": "body", "columnDefinition": "TEXT" },
|
||||
{ "name": "noteType", "javaType": "NoteTypeEnum", "nullable": false, "columnName": "note_type" },
|
||||
{ "name": "streamTypes", "javaType": "String[]", "nullable": true, "columnName": "stream_types", "columnDefinition": "text[]", "jdbcTypeCode": "ARRAY" }
|
||||
],
|
||||
"elementCollections": [
|
||||
{
|
||||
"fieldName": "tags",
|
||||
"collectionTable": "training_note_tag",
|
||||
"joinColumn": "training_note_id",
|
||||
"valueType": "String",
|
||||
"valueColumn": "tag",
|
||||
"orderColumn": null,
|
||||
"embeddable": null
|
||||
},
|
||||
{
|
||||
"fieldName": "attachments",
|
||||
"collectionTable": "training_note_attachment",
|
||||
"joinColumn": "training_note_id",
|
||||
"valueType": "NoteAttachment",
|
||||
"valueColumn": null,
|
||||
"orderColumn": "sort_index",
|
||||
"embeddable": {
|
||||
"className": "NoteAttachment",
|
||||
"fields": [
|
||||
{ "name": "url", "javaType": "String", "nullable": false, "columnName": "url" },
|
||||
{ "name": "filename", "javaType": "String", "nullable": true, "columnName": "filename" }
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"oneToManyRelations": [
|
||||
{
|
||||
"fieldName": "steps",
|
||||
"entityClass": "NoteStep",
|
||||
"joinColumn": "training_note_id",
|
||||
"orderBy": "sortIndex ASC",
|
||||
"fetchMode": "SUBSELECT"
|
||||
}
|
||||
],
|
||||
"enums": [
|
||||
{
|
||||
"className": "NoteTypeEnum",
|
||||
"values": [
|
||||
{ "name": "GENERAL", "key": 1 },
|
||||
{ "name": "RACE", "key": 2 },
|
||||
{ "name": "RECOVERY", "key": 3 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user