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/parameter
openapi/request_body
openapi/response
openapi/schema
They 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.