In this guide you will learn how to set up the JStatic as a Gradle task. We will then take it to a more in-depth guide on how to use JStatic to generate Java source code from OpenAPI spec as a concrete example.
Setting up JStatic as a Gradle task is pretty simple and straight forward:
configurations {
jstaticRuntime
}
dependencies {
// TODO replace with desired version
jstaticRuntime 'com.mohamnag:jstatic:X.Y.Z'
}
task jstaticRuntime(type: JavaExec) {
group = "Build"
classpath = configurations.jstaticRuntime
main = 'com.mohamnag.jstatic.Application'
// TODO replace with the path to your config.yaml file
args "--config=jstatic-config.yaml"
}
Above snippet:
jstaticRuntime. We do this not to potentially pollute the configurations for runtime, compile or else with necessary dependencies for running JStatic and to avoid any version conflict there.While you might potentially use JStatic as a static HTML content generator integrated in your Gradle build process, there are more exciting use-cases possible for it. We will show you here how to use it to generate Java POJOs out of an OpenAPI specification file fully customizable to your needs.
To keep things separate, we will create a dedicated directory for our code generation and will call it codegen.
As first step we add a codegen.gradle file to that directory with following content:
// FILE: codegen/codegen.gradle
configurations {
openApiCodegen
}
dependencies {
// TODO replace with desired version
openApiCodegen 'com.mohamnag:jstatic:X.Y.Z'
}
sourceSets {
// TODO replace with output path that you will setup later in your config.yaml file
main.java.srcDirs += "${buildDir}/generated/api/src/main/java/"
}
task openApiCodegen(type: JavaExec) {
group = "Build"
classpath = configurations.openApiCodegen
main = 'com.mohamnag.jstatic.Application'
// TODO replace with the path to your config.yaml file
args "--config=codegen/config.yaml"
}
compileJava.dependsOn openApiCodegen
This script does many things similar to what we had in the simple task setup above but in addition:
"${buildDir}/generated/api/src/main/java/") to sourceSets so that the generated cod will be considered by the Java compilercompileJava task and the defined task openApiCodegen so that the latter is always run before compilation phaseAlso pay attention that all paths in this file are still relative to the project root.
Next we will add following line to somewhere in the root build.gradle file:
// other stuff in project's main gradle file
apply from: 'codegen/codegen.gradle'
// other stuff in project's main gradle file
This makes sure that the partial snippet from above is applied to the main Gradle flow.
Now we will configure the JStatic. Add a config.yaml file to inside codegen directory with following content:
plugins:
LOAD_DATA_TREE:
- com.mohamnag.jstatic.plugins.front_matter_loader.MarkdownFrontMatterLoaderTask
- com.mohamnag.jstatic.plugins.env_var_loader.EnvVariablesLoader
- com.mohamnag.jstatic.plugins.openapi_processor.OpenApiProcessor
PROCESS_ASSETS:
COMPILE_TEMPLATES:
- com.mohamnag.jstatic.plugins.handlebars_template_compiler.HandlebarsTemplateCompilerTask
DUMP_TREE:
- com.mohamnag.jstatic.plugins.html_file_dumper.FileDumperTask
front-matter-loader:
data-dir: codegen/supplementary/
yaml-loader:
data-dir: src/main/resources/
handlebars-template-compiler:
base-dir: codegen/templates/
default-template: default
target-data-field: compiledHandlebars
template-data-field: template
start-node:
- com
- example
- project
helpers:
- com.mohamnag.jstatic.plugins.handlebars_template_compiler.helpers.UrlHelpers
- com.mohamnag.jstatic.plugins.handlebars_template_compiler.helpers.StringHelpers
- codegen/templates/helpers.js
file-dumper:
output-dir: build/generated/api/src/main/java/
data-field: compiledHandlebars
output-file-extension: .java
open-api-processor:
component-template-parameters: openapi/parameter
component-template-request-bodies: openapi/request_body
component-template-responses: openapi/response
component-template-schemas: openapi/schema
component-package-parameters: com.example.project.dtos
component-package-request-bodies: com.example.project.dtos
component-package-responses: com.example.project.dtos
component-package-schemas: com.example.project.dtos.schemas
This config:
OpenApiProcessor (which loads OpenAPI data into the tree) but we also enable some more:
EnvVariablesLoader will enable overriding some of the values using environment variables.MarkdownFrontMatterLoaderTask will enable us generate additional files based on input outside of OpenAPI specHandlebarsTemplateCompilerTask to compile templates based on data that was loadedFileDumperTask is enabled to dump the template compilation output on the diskTo know more about details of what and how OpenApiProcessor works, take a look at its documentation here.
We defined following templates to be used in our configuration:
openapi/parameteropenapi/request_bodyopenapi/responseopenapi/schemaThey will all be located under codegen/templates/ directory relative to the root of the project.
// default.hbs
{{page.data.body}}
/*
!! page.data
{{#each page.data}}
{{@key}}: {{this}}
{{/each}}
!! this
{{#each this}}
{{@key}}: {{this}}
{{/each}}
*/
// openapi/parameter.hbs
{{>default}}
// openapi/request_body.hbs
{{>default}}
// openapi/response.hbs
{{>default}}
// openapi/schema.hbs
{{#with page.data.component as |component|}}
{{#if (eq component.type 'object')}}
{{> openapi/pojo
className=page.name
component=component
}}
{{else}}
{{#if (eq component.type 'array')}}
// arrays on root of schema not supported, yet!
{{else}}
{{#if (and component.enumValues (eq component.type 'string'))}}
{{> openapi/enum
className=page.name
component=component
}}
{{else}}
{{#if component.type}}
{{> openapi/valueObject
className=page.name
component=component
}}
{{/if}}
{{/if}}
{{/if}}
{{/if}}
{{#unless component.type}}
// you forgot to set type on a component!
{{/unless}}
{{/with}}
We will not dive deeper here as this is already getting too long, however you can see that above templates mostly use the default template which will just dump the important data under page and this so that you can build up on these.
The last template above, schema.hbs, is an example of how you can examine and differ to other templates based on type of component that you have got.
The last template we want to show here is enum.hbs:
package {{component.package}};
public enum {{name}} {
{{#each component.enumValues}}
{{this}},
{{/each}}
;
}
This is a very simple example of how to build a valid Java file (an enum class) out of values parsed from OpenAPI.
Here, we set up the base for generating Java code from OpenAPI spec files. Templates that we used need much more tuning, and you will need more of those templates to be able to generate a functional example. The good thing is that it is very flexible what you want to generate and how you want to do it. If you only need parts of it, you can omit the rest. If you want to rename things or bend them a bit, it's all yours to decide.