Search code examples
javaspring-bootenumscode-generationjooq

How build a JOOQ custom generator?


I have a specific Postgre schema that gathers all tables that defines types, like Status(name, description) where values could be OPEN, Open item status, CLOSED, Closed item status, etc.

We need to fetch all these tables and generate enums based on them to later use it within our app. So, these enums should look like:

enum Status {
    OPEN("Open item status"),
    CLOSED("Closed item status")
    ...
}

We decided to use JOOQ which looks pretty interesting, but we cannot find documentation/examples to create a custom generator that use default java generator behavior plus a custom enum generation extension.

Basing on this post generate enum class from table with JOOQ, it brought some ideas but still not clear what to do to achieve what the answer states.

Update: in order to have my custom generator to being picked by jooq-codegen-maven plugin I created a separate project and added its jar as a dependency of my parent project. I created a class MyGenerator and made it extend from JavaGenerator. In order to have this org.jooq.codegen.JavaGenerator I had to add below maven dependency (not coming in spring-boot-starter-jooq):

<dependency>
    <groupId>org.jooq</groupId>
    <artifactId>jooq-codegen-maven</artifactId>
    <version>3.11.2</version>
</dependency>

Once done this and inherited from JavaGenerator, according to the post answer I should hook into generate(SchemaDefinition) method, however this is not protected scope, so I think I'm missing something here.

Do you know or can you provide an example describing how to write this kind of code generator for enums? I need JOOQ to generate code as usual for a specific schema, but besides it I need to query another specific "enum" schema that only contains those "enum" tables.


Solution

  • I know this is an old question, but I'm posting my answer since it might be useful for others.

    I had to face the same needs and it was very difficult to achieve, so here you can find the code I implemented to generate enums from an enums schema.

    The code was implemented in groovy, but for java it's very similar.

    First and very important, I had to create a separate project for my enum generator since it will work as a dependency for the project that is going to use it. This is needed because the project generating the code must run the enum generator at compilation time, so the way to achieve this is by adding the enum generator as a dependency.

    Enum generator project dependency

    package com.ctg.jooqgenerator.jooq
    
    import org.jooq.codegen.JavaGenerator
    import org.jooq.codegen.JavaWriter
    import org.jooq.meta.Database
    import org.jooq.meta.SchemaDefinition
    import org.jooq.meta.TableDefinition
    import org.slf4j.Logger
    import org.slf4j.LoggerFactory
    
    import java.sql.ResultSet
    
    class EnumGenerator extends JavaGenerator {
        private static final String ENUMS_SCHEMA = "enums"
    
        private static final Logger log = LoggerFactory.getLogger(EnumGenerator.class)
    
        @Override
        void generateSchema(SchemaDefinition schema) {
            // Apply custom logic only for `enums` schema. Others schema has regular generation
            if (schema.name != ENUMS_SCHEMA) {
                super.generateSchema(schema)
                return
            }
    
            log.info("Generating enums")
            log.info("----------------------------------------------------------")
    
            Database db = schema.database
    
            db.getTables(schema).each { TableDefinition table ->
                // Prepare enum name from snake_case to CamelCase
                String enumName = table.name.replaceAll('_([a-z])') { it[1].capitalize() }.capitalize()
    
                JavaWriter out = newJavaWriter(new File(getFile(schema).getParentFile(), "${enumName}.java"))
                log.info("Generating enum: {}.java [input={}, output={}]", enumName, table.name, enumName)
    
                printPackage(out, schema)
    
                out.println("public enum $enumName {")
    
                ResultSet rs = db.connection.prepareStatement("SELECT * FROM ${schema}.\"${table.name}\"").executeQuery()
                while (rs.next()) {
                    String name = rs.getString('name'),
                           description = rs.getString('description'),
                           s = rs.isLast() ? ";" : ","
    
                    // Generate enum entry
                    out.tab(1).println("$name(\"$description\")$s")
                }
    
                out.println("""
                |    private final String description;
                |
                |    private $enumName(String description) {
                |        this.description = description;
                |    }
                |}
                """.stripMargin())
    
                closeJavaWriter(out)
            }
    
            log.info("----------------------------------------------------------")
            super.generateSchema(schema)
        }
    }
    

    Database having enum tables

    The tables that will be translated to enums look like this:

    -- Table name `account_role` will be translated into `AccountRole`
    CREATE TABLE enums.account_role (
        "name" varchar(100) NOT NULL,
        description varchar(255) NOT NULL,
        CONSTRAINT account_role_name_key UNIQUE (name)
    );
    
    -- Table entries will be translated into enum entries
    INSERT INTO enums.account_role ("name",description) VALUES 
    ('BILLING','Role for contact/address that will be a Billing contact/address'),
    ('PAYMENT','Role for contact/address that will be a Payment contact/address'),
    ('SERVICE','Role for contact/address that will be a Service contact/address'),
    ('SOLD_TO','Role for contact/address that will be a SoldTo contact/address')
    ;
    

    This data definition will result in below autogenerated enum AccountRole.java:

    /*
     * This file is generated by jOOQ.
     */
    package com.congerotechnology.ctgcommon.jooq.enums;
    
    public enum AccountRole {
        BILLING("Role for contact/address that will be a Billing contact/address"),
        PAYMENT("Role for contact/address that will be a Payment contact/address"),
        SERVICE("Role for contact/address that will be a Service contact/address"),
        SOLD_TO("Role for contact/address that will be a SoldTo contact/address");
    
        private final String description;
    
        private AccountRole(String description) {
            this.description = description;
        }
    }
    

    Main project

    Then on the main project that is going to use this enum generator I have set the following maven code on pom.xml:

    <dependencies>
    ...
        <!-- JOOQ custom generator -->
        <dependency>
           <groupId>com.ctg</groupId>
           <artifactId>ctg-jooq-generator</artifactId>
           <version>0.0.1</version>
        </dependency>
    ...
    </dependencies>
    
    <build>
    ...
        <plugins>
            <!-- JOOQ code generation -->
            <plugin>
                <groupId>org.jooq</groupId>
                <artifactId>jooq-codegen-maven</artifactId>
                <version>${jooq.version}</version>
    
                <executions>
                    <execution>
                        <id>generate-sources</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
    
                <configuration>
                    <jdbc>
                        <driver>org.postgresql.Driver</driver>
                        <url>jdbc:postgresql://${env.DB_URL}</url>
                        <user>${env.DB_USER}</user>
                        <password>${env.DB_PASSWORD}</password>
                    </jdbc>
                    <generator>
                        <name>com.ctg.ctgjooqgenerator.jooq.EnumGenerator</name>
    
                        <database>
                            <name>org.jooq.meta.postgres.PostgresDatabase</name>
                            <includes>.*</includes>
                            <excludes />
                            <dateAsTimestamp>true</dateAsTimestamp>
                            <inputSchema>enums</inputSchema>
                        </database>
                        <generate>
                            <deprecated>false</deprecated>
                            <instanceFields>true</instanceFields>
                        </generate>
                        <target>
                            <packageName>com.ctg.ctgcommon.jooq.enums</packageName>
                            <directory>target/generated-sources/jooq-postgres</directory>
                        </target>
                    </generator>
                </configuration>
    
                <dependencies>
                    <dependency>
                        <groupId>org.postgresql</groupId>
                        <artifactId>postgresql</artifactId>
                        <version>${postgresql.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>