Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions codegen-superclass-test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/.classpath
/.gradle/
/.project
/.settings/
/bin/
/build/
/target/
/.metadata
/.idea/
.factorypath
/.claude/
/data/
/.apt_generated/

/src/main/java/codegen
/src/main/kotlin/codegen
/src/main/resources/META-INF/codegen
/src/test/java/codegen
/src/test/kotlin/codegen
97 changes: 97 additions & 0 deletions codegen-superclass-test/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
plugins {
java
alias(libs.plugins.doma.compile)
id("org.domaframework.doma.codegen")
}

java {
toolchain.languageVersion.set(JavaLanguageVersion.of(17))
}

repositories {
mavenLocal()
mavenCentral()
}

dependencies {
implementation(libs.doma.core)
annotationProcessor(libs.doma.processor)

testImplementation(platform(libs.junit.bom))
testImplementation(libs.junit.jupiter.api)
testRuntimeOnly(libs.junit.jupiter.engine)
testRuntimeOnly(libs.junit.platform.launcher)

testImplementation(libs.h2)

domaCodeGen(libs.h2)
// Add compiled classes to domaCodeGen classpath for superclass resolution
domaCodeGen(sourceSets.main.get().output)
}

val initScript = file("init_h2.sql")
val _url = "jdbc:h2:mem:superclass_test;INIT=RUNSCRIPT FROM 'file:${initScript.absolutePath}'"
val _user = ""
val _password = ""

domaCodeGen {
register("superclass") {
val basePackage = "codegen"
url = _url
user = _user
password = _password
schemaName = "PUBLIC"
entity {
packageName = "${basePackage}.entity"
// Use AbstractEntity as superclass
superclassName = "base.AbstractEntity"
// Use AbstractEntityListener as listener superclass
listenerSuperclassName = "base.AbstractEntityListener"
}
dao {
packageName = "${basePackage}.dao"
}
}
}

tasks {
// Step 1: Create a task to compile only base classes first
val compileBaseClasses = register<JavaCompile>("compileBaseClasses") {
source = fileTree("src/main/java") {
include("**/base/**")
}
classpath = configurations.compileClasspath.get()
destinationDirectory.set(layout.buildDirectory.dir("classes/java/main"))
}

// Step 2: domaCodeGen depends on base classes being compiled
matching { it.name.startsWith("domaCodeGen") }.configureEach {
dependsOn(compileBaseClasses)
}

// Step 3: Full compilation depends on code generation
compileJava {
dependsOn("domaCodeGenSuperclassAll")
}

test {
useJUnitPlatform()
}

val deleteSrc = register("deleteSrc") {
doLast {
// Delete only generated code, not base classes
delete("src/main/java/codegen")
delete("src/main/kotlin/codegen")
delete("src/main/resources/META-INF/codegen")
delete("src/test/java/codegen")
delete("src/test/kotlin/codegen")
}
}

clean {
dependsOn(deleteSrc)
}
}


52 changes: 52 additions & 0 deletions codegen-superclass-test/init_h2.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
-- Create test tables with superclass fields

CREATE TABLE EMPLOYEE (
ID BIGINT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(100) NOT NULL,
EMAIL VARCHAR(100),
DEPARTMENT VARCHAR(50),
SALARY DECIMAL(10, 2),
-- Superclass fields
CREATED_BY VARCHAR(50),
CREATED_AT TIMESTAMP,
UPDATED_BY VARCHAR(50),
UPDATED_AT TIMESTAMP,
DELETED BOOLEAN DEFAULT FALSE
);

CREATE TABLE DEPARTMENT (
ID BIGINT PRIMARY KEY AUTO_INCREMENT,
DEPT_NAME VARCHAR(100) NOT NULL,
LOCATION VARCHAR(100),
-- Superclass fields
CREATED_BY VARCHAR(50),
CREATED_AT TIMESTAMP,
UPDATED_BY VARCHAR(50),
UPDATED_AT TIMESTAMP,
DELETED BOOLEAN DEFAULT FALSE
);

CREATE TABLE PROJECT (
ID BIGINT PRIMARY KEY AUTO_INCREMENT,
PROJECT_NAME VARCHAR(200) NOT NULL,
START_DATE DATE,
END_DATE DATE,
BUDGET DECIMAL(15, 2),
-- Superclass fields
CREATED_BY VARCHAR(50),
CREATED_AT TIMESTAMP,
UPDATED_BY VARCHAR(50),
UPDATED_AT TIMESTAMP,
DELETED BOOLEAN DEFAULT FALSE
);

-- Insert sample data
INSERT INTO EMPLOYEE (NAME, EMAIL, DEPARTMENT, SALARY, CREATED_BY, CREATED_AT, UPDATED_BY, UPDATED_AT, DELETED)
VALUES ('John Doe', 'john@example.com', 'Engineering', 75000.00, 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, FALSE);

INSERT INTO DEPARTMENT (DEPT_NAME, LOCATION, CREATED_BY, CREATED_AT, UPDATED_BY, UPDATED_AT, DELETED)
VALUES ('Engineering', 'Building A', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, FALSE);

INSERT INTO PROJECT (PROJECT_NAME, START_DATE, END_DATE, BUDGET, CREATED_BY, CREATED_AT, UPDATED_BY, UPDATED_AT, DELETED)
VALUES ('Project Alpha', '2026-01-01', '2026-12-31', 500000.00, 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, FALSE);

72 changes: 72 additions & 0 deletions codegen-superclass-test/src/main/java/base/AbstractEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package base;

import java.io.Serializable;
import java.time.LocalDateTime;
import org.seasar.doma.Column;

/**
* Base entity class with common audit fields.
* This class demonstrates superclass functionality in Doma CodeGen.
*/
public abstract class AbstractEntity implements Serializable {

/** Created by user ID */
@Column(name = "CREATED_BY")
private String createdBy;

/** Created timestamp */
@Column(name = "CREATED_AT")
private LocalDateTime createdAt;

/** Last updated by user ID */
@Column(name = "UPDATED_BY")
private String updatedBy;

/** Last updated timestamp */
@Column(name = "UPDATED_AT")
private LocalDateTime updatedAt;

/** Logical delete flag */
@Column(name = "DELETED")
private Boolean deleted;

public String getCreatedBy() {
return createdBy;
}

public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}

public LocalDateTime getCreatedAt() {
return createdAt;
}

public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}

public String getUpdatedBy() {
return updatedBy;
}

public void setUpdatedBy(String updatedBy) {
this.updatedBy = updatedBy;
}

public LocalDateTime getUpdatedAt() {
return updatedAt;
}

public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}

public Boolean getDeleted() {
return deleted;
}

public void setDeleted(Boolean deleted) {
this.deleted = deleted;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package base;

import java.time.LocalDateTime;
import org.seasar.doma.jdbc.entity.EntityListener;
import org.seasar.doma.jdbc.entity.PreInsertContext;
import org.seasar.doma.jdbc.entity.PreUpdateContext;

/**
* Base entity listener that automatically sets audit fields.
*/
public class AbstractEntityListener<E extends AbstractEntity> implements EntityListener<E> {

@Override
public void preInsert(E entity, PreInsertContext<E> context) {
LocalDateTime now = LocalDateTime.now();
entity.setCreatedAt(now);
entity.setUpdatedAt(now);
entity.setCreatedBy("system");
entity.setUpdatedBy("system");
if (entity.getDeleted() == null) {
entity.setDeleted(false);
}
}

@Override
public void preUpdate(E entity, PreUpdateContext<E> context) {
entity.setUpdatedAt(LocalDateTime.now());
entity.setUpdatedBy("system");
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ private void connectProperties(CodeGenEntityDescTask task, CodeGenConfig extensi
task.getVersionColumnNamePattern().set(extension.getVersionColumnNamePattern());
task.getLanguageClassResolver().set(extension.getLanguageClassResolver());
task.setEntityConfig(extension.getEntityConfig());
task.setCodeGenClassLoaderProvider(extension.getClassLoaderProvider());
}

private void connectProperties(CodeGenEntityTask task, CodeGenConfig extension) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public class CodeGenConfig {

private final String name;

private final Project project;

private final Configuration configuration;

private final Property<GlobalFactory> globalFactory;
Expand Down Expand Up @@ -93,6 +95,7 @@ public class CodeGenConfig {
@Inject
public CodeGenConfig(String name, Project project) {
this.name = name;
this.project = project;

this.configuration = project.getConfigurations().getByName(CONFIGURATION_NAME);

Expand Down Expand Up @@ -473,4 +476,21 @@ public void sql(Action<SqlConfig> action) {
public void sqlTest(Action<SqlTestConfig> action) {
action.execute(sqlTestConfig);
}

/**
* Returns a Provider that creates a ClassLoader including the domaCodeGen configuration classpath.
* This can be used by tasks to load classes specified in entity configuration.
*
* <p>The ClassLoader is created lazily when the Provider is resolved (during task execution),
* ensuring that all dependencies in the domaCodeGen configuration are properly resolved.
*
* <p>Note: If the domaCodeGen configuration is empty or null, the class's own ClassLoader is returned.
* Otherwise, each call to {@code get()} creates a new URLClassLoader instance. In the latter case,
* the caller is responsible for managing the lifecycle of the returned ClassLoader.
*
* @return a Provider that creates a ClassLoader including the domaCodeGen configuration classpath
*/
public Provider<ClassLoader> getClassLoaderProvider() {
return project.provider(this::createClassLoader);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ public class CodeGenEntityDescTask extends DefaultTask {
private final Property<LanguageClassResolver> languageClassResolver =
getProject().getObjects().property(LanguageClassResolver.class);

private Provider<ClassLoader> codeGenClassLoaderProvider;

private EntityConfig entityConfig;

@Internal
Expand Down Expand Up @@ -121,6 +123,15 @@ public void setEntityConfig(EntityConfig entityConfig) {
this.entityConfig = entityConfig;
}

@Internal
public Provider<ClassLoader> getCodeGenClassLoaderProvider() {
return codeGenClassLoaderProvider;
}

public void setCodeGenClassLoaderProvider(Provider<ClassLoader> codeGenClassLoaderProvider) {
this.codeGenClassLoaderProvider = codeGenClassLoaderProvider;
}

@TaskAction
public void create() {
EntityPropertyClassNameResolver entityPropertyClassNameResolver =
Expand Down Expand Up @@ -165,7 +176,10 @@ private EntityDescFactory createEntityDescFactory(
EntityPropertyDescFactory entityPropertyDescFactory) {
Class<?> superclass = null;
if (entityConfig.getSuperclassName().isPresent()) {
superclass = ClassUtil.forName(entityConfig.getSuperclassName().get(), "superclassName");
ClassLoader classLoader = codeGenClassLoaderProvider != null && codeGenClassLoaderProvider.isPresent()
? codeGenClassLoaderProvider.get()
: getClass().getClassLoader();
superclass = ClassUtil.forName(entityConfig.getSuperclassName().get(), "superclassName", classLoader);
}
return globalFactory
.get()
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ if (releaseVersion != null) {
include("codegen-h2-test")
include("codegen-tc-test")
include("codegen-template-test")
include("codegen-superclass-test")
}