Custom generator to generate enums

646 views
Skip to first unread message

federic...@gmail.com

unread,
Sep 5, 2018, 1:49:26 PM9/5/18
to jOOQ User Group
Hi guys,

I'm very stuck trying to create a custom generator. Have been investigating a lot regarding this and found this post on SO that is a similar problem to mine:

This post answer by Lukas, give a good idea of what I should do, however I'd need better details of implementation since I have no clue how to continue. I've created this SO question describing what I need in details https://stackoverflow.com/questions/52171377/how-build-a-jooq-custom-generator. Basically, I have a postgres schema named "enums" where all the tables are like TYPES("name", "description"), and I have to create all groovy (or java) enums like below:
enum Types {
    OPEN("open status"), CLOSE("close status");

    private final String id;
    Types(String id) { this.id = id; }
    public String getValue() { return id; }
}

According the post answer I have to create a class extending from JavaGenerator, however I was even unable to trigger my custom generator from the maven plugin. So, by using `mvn -e jooq-codegen:generate` I found this error:
Caused by: java.lang.ClassNotFoundException: com.ctgengine.customer.jooq.EnumGenerator
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        at org.jooq.codegen.GenerationTool.loadClass(GenerationTool.java:819)


In the maven plugin I have this snippet:
<generator>
<!--<name>org.jooq.codegen.JavaGenerator</name>-->
<name>com.ctgengine.customer.jooq.EnumGenerator</name>
...

And in my class I have:
class EnumGenerator extends JavaGenerator {
@Override
void generateTables(SchemaDefinition schema) {
....

Anyway, if I uncomment the JavaGenerator everything works fine.

So, I have two big gaps of knowledge where I need your help:
- Why my code isn't being picked by the maven plugin?
- Once I could link the plugin to my code, how can I do to generate groovy (or java) enums?

Thanks a lot in advance.

Lukas Eder

unread,
Sep 5, 2018, 3:04:03 PM9/5/18
to jooq...@googlegroups.com
Hi Federico

This is a common issue when creating some custom implementation of a code generator extension and expecting the jOOQ GenerationTool to pick it up from the classpath. It has to be ... on the classpath. So, this is more of a Maven question than a jOOQ related one. For your custom EnumGenerator to end up on the GenerationTool's classpath (or rather, the generator plugin's classpath), you have to add it as a dependency. This is similar to when you want to use the JPADatabase and have it pick up JPA annotated dependencies from the classpath:

Essentially, just:

1. Create a separate Maven project containing your EnumGenerator
2. Add that project as a dependency to either your "main" project that runs the code generator, or to the Maven plugin configuration for the code generator

Note: You cannot place the EnumGenerator in the same project that runs the code generator plugin, because the code generation phase is "generate-sources", which happens before the "compile" phase in Maven. So your EnumGenerator is simply not yet compiled and installed, when you expect the code generator to consume it.

Hope this helps,
Lukas

--
You received this message because you are subscribed to the Google Groups "jOOQ User Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jooq-user+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

federic...@gmail.com

unread,
Sep 5, 2018, 5:47:24 PM9/5/18
to jOOQ User Group
Hi Lukas,

Thanks a lot. It has totally sense. I've created the separated project, added it as a dependency and I could trigger my custom generator (actually I'm debugging it to learn more), so first knowledge gap closed.

Now I fall in the real problem that is how to generate the enums using a specific schema. I'm aware that JOOQ 2.x had this support but was removed since it was very complex, but my use case is pretty specific and look much more simpler.

Can you provide a some details about how to do it?

Thanks!
Federico

Lukas Eder

unread,
Sep 6, 2018, 1:50:38 AM9/6/18
to jooq...@googlegroups.com
Hi Federico,

Thanks for your feedback. Glad you got your generator working with Maven.

Indeed, there was support for generating custom enums from tables in jOOQ 2.x. The problem wasn't strictly complexity, but the fact that there are about 20 different ways how one could want to generate such enums. The 2.x feature simply didn't respond to all those requirements.

I'm guessing you need help to figure out how to generate your own classes right now. Your EnumGenerator should now override one of the generate() methods (e.g. generate(Database) or generateCatalog(...) or generateSchema(...)), which should call the super implementation, and then proceed to generating your enums. Here, you're entirely on your own, which means you have complete freedom in how to design your class.

Some useful hints:

- Use JavaGenerator.newJavaWriter() to get a hold of a writer. It takes care of handling imports for you and has some convenience API. Also, it only writes the class if there has been a modification to the previous code generation, allowing for incremental compilation
- Use the Database.getConnection() method to get a hold of a JDBC connection. You can use it to query your tables for enum values and descriptions and generate code from that.

Then, you will probably need to switch to programmatic code generator configuration in order to dynamically create ForcedType configurations to replace the references to those enum tables by the actual enum types:


I hope this helps,
Lukas

Federico Piazza

unread,
Sep 7, 2018, 12:46:45 PM9/7/18
to jooq...@googlegroups.com
MailTag
Hi Lukas,

Thanks a lot. I have followed your suggestion and could come up with something (I hope useful). I still remain to make the stuff about the forced types, but I can move forward for the moment.

I'll post my solution for other people might find it useful since I struggled a lot against this. By the way, if you see something very bad, please feel free to suggest any tip.

Here is the code that is generating my custom enums out of my db:
package com.ctgcommon.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 custom 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 custom enum: {}.java for table '{}'", enumName, table.name)

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)
}
}

Hope to help.
Federico

You received this message because you are subscribed to a topic in the Google Groups "jOOQ User Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/jooq-user/5lfiz8yFDh0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to jooq-user+...@googlegroups.com.

Lukas Eder

unread,
Sep 10, 2018, 7:11:13 AM9/10/18
to jooq...@googlegroups.com
Thanks a lot for sharing this. Will definitely be useful to others.

What language is this? Xtend? Groovy?

I do see something worth improving. Note you can wrap your JDBC Connection using DSL.using(Connection) to get a jOOQ DSLContext and run statements there. It will help with auto-closing of resources, which you omitted. If the code generation is large, then omitting closing all result sets and prepared statements could definitely cause issues.

Hope this helps,
Lukas

Federico Piazza

unread,
Sep 10, 2018, 10:36:41 AM9/10/18
to jooq...@googlegroups.com
MailTag
Hey Lukas,

Thanks for pointing out that, will take care of those improvements.

Regarding the language, I'm using Groovy 2.5 with jdk 8.

Thanks!
Federico


Reply all
Reply to author
Forward
0 new messages