[bug] Templates via classpath (#5164)

* Fixes issue with templates loading via classpath

The templating engines were originally written to load templates via the
classpath, but this functionality was blocked by file-only checks
further up the stack. This loosens those file-only checks, allowing
files and relatively imported files to be included via classpath.

* [docs] Add details about classpath-level templates

* [feat][maven] templateResourcePath for template on classpath
  - NOTE templateResourcePath is not needed for gradle, as it accepts
    a string for the target template directory, which supports classpath
This commit is contained in:
Jim Schubert 2020-01-31 17:36:06 -05:00 committed by GitHub
parent 22c6c0ca68
commit 507f80617d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 60 additions and 5 deletions

View File

@ -26,6 +26,29 @@ OpenAPI Generator supports user-defined templates. This approach is often the ea
> **Note:** You cannot use this approach to create new templates, only override existing ones. If you'd like to create a new generator to contribute back to the project, see `new.sh` in the repository root. If you'd like to create a private generator for more templating control, see the [customization](./customization.md) docs. > **Note:** You cannot use this approach to create new templates, only override existing ones. If you'd like to create a new generator to contribute back to the project, see `new.sh` in the repository root. If you'd like to create a private generator for more templating control, see the [customization](./customization.md) docs.
OpenAPI Generator not only supports local files for templating, but also templates defined on the classpath. This is a great option if you want to reuse templates across multiple projects. To load a template via classpath, you'll need to generate a little differently. For example, if you've created an artifact called `template-classpath-example` which contains extended templates for the `htmlDocs` generator with the following structure:
```
└── src
├── main
│   ├── java
│   └── resources
│   └── templates
│   └── htmlDocs
│   ├── index.mustache
│   └── style.css.mustache
```
You can define your classpath to contain your JAR and the openapi-generator-cli _fat jar_, then invoke main class `org.openapitools.codegen.OpenAPIGenerator`. For instance,
```sh
java -cp /path/totemplate-classpath-example-1.0-SNAPSHOT.jar:modules/openapi-generator-cli/target/openapi-generator-cli.jar \
org.openapitools.codegen.OpenAPIGenerator generate -i https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v3.0/petstore.yaml \
-g html -o template-example -t templates/htmlDocs
```
Note that our template directory is relative to the resource directory of the JAR defined on the classpath.
### Custom Logic ### Custom Logic
For this example, let's modify a Java client to use AOP via [jcabi/jcabi-aspects](https://github.com/jcabi/jcabi-aspects). We'll log API method execution at the `INFO` level. The jcabi-aspects project could also be used to implement method retries on failures; this would be a great exercise to further play around with templating. For this example, let's modify a Java client to use AOP via [jcabi/jcabi-aspects](https://github.com/jcabi/jcabi-aspects). We'll log API method execution at the `INFO` level. The jcabi-aspects project could also be used to implement method retries on failures; this would be a great exercise to further play around with templating.

View File

@ -21,6 +21,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -406,15 +409,29 @@ public class WorkflowSettings {
*/ */
public Builder withTemplateDir(String templateDir) { public Builder withTemplateDir(String templateDir) {
if (templateDir != null) { if (templateDir != null) {
URI uri = null;
File f = new File(templateDir); File f = new File(templateDir);
// check to see if the folder exists // check to see if the folder exists
if (!(f.exists() && f.isDirectory())) { if (f.exists() && f.isDirectory()) {
uri = f.toURI();
this.templateDir = Paths.get(uri).toAbsolutePath().toString();
} else {
URL url = this.getClass().getClassLoader().getResource(templateDir);
if (url != null) {
try {
uri = url.toURI();
this.templateDir = templateDir;
} catch (URISyntaxException e) {
LOGGER.warn("The requested template was found on the classpath, but resulted in a syntax error.");
}
}
}
if (uri == null) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Template directory " + templateDir + " does not exist."); "Template directory " + templateDir + " does not exist.");
} }
this.templateDir = Paths.get(f.toURI()).toAbsolutePath().toString();
} }
return this; return this;

View File

@ -48,6 +48,7 @@ mvn clean compile
| `generatorName` | `openapi.generator.maven.plugin.generatorName` | target generator name | `generatorName` | `openapi.generator.maven.plugin.generatorName` | target generator name
| `output` | `openapi.generator.maven.plugin.output` | target output path (default is `${project.build.directory}/generated-sources/openapi`. Can also be set globally through the `openapi.generator.maven.plugin.output` property) | `output` | `openapi.generator.maven.plugin.output` | target output path (default is `${project.build.directory}/generated-sources/openapi`. Can also be set globally through the `openapi.generator.maven.plugin.output` property)
| `templateDirectory` | `openapi.generator.maven.plugin.templateDirectory` | directory with mustache templates | `templateDirectory` | `openapi.generator.maven.plugin.templateDirectory` | directory with mustache templates
| `templateResourcePath` | `openapi.generator.maven.plugin.templateResourcePath` | directory with mustache templates via resource path. This option will overwrite any option defined in `templateDirectory`.
| `addCompileSourceRoot` | `openapi.generator.maven.plugin.addCompileSourceRoot` | add the output directory to the project as a source root (`true` by default) | `addCompileSourceRoot` | `openapi.generator.maven.plugin.addCompileSourceRoot` | add the output directory to the project as a source root (`true` by default)
| `modelPackage` | `openapi.generator.maven.plugin.modelPackage` | the package to use for generated model objects/classes | `modelPackage` | `openapi.generator.maven.plugin.modelPackage` | the package to use for generated model objects/classes
| `apiPackage` | `openapi.generator.maven.plugin.apiPackage` | the package to use for generated api objects/classes | `apiPackage` | `openapi.generator.maven.plugin.apiPackage` | the package to use for generated api objects/classes

View File

@ -40,6 +40,7 @@ import java.util.Set;
import com.google.common.io.ByteSource; import com.google.common.io.ByteSource;
import com.google.common.io.CharSource; import com.google.common.io.CharSource;
import io.swagger.v3.parser.util.ClasspathHelper; import io.swagger.v3.parser.util.ClasspathHelper;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.LifecyclePhase;
@ -133,6 +134,12 @@ public class CodeGenMojo extends AbstractMojo {
@Parameter(name = "templateDirectory", property = "openapi.generator.maven.plugin.templateDirectory") @Parameter(name = "templateDirectory", property = "openapi.generator.maven.plugin.templateDirectory")
private File templateDirectory; private File templateDirectory;
/**
* Resource path containing template files.
*/
@Parameter(name = "templateResourcePath", property = "openapi.generator.maven.plugin.templateResourcePath")
private String templateResourcePath;
/** /**
* The name of templating engine to use, "mustache" (default) or "handlebars" (beta) * The name of templating engine to use, "mustache" (default) or "handlebars" (beta)
*/ */
@ -583,6 +590,13 @@ public class CodeGenMojo extends AbstractMojo {
configurator.setTemplateDir(templateDirectory.getAbsolutePath()); configurator.setTemplateDir(templateDirectory.getAbsolutePath());
} }
if (StringUtils.isNotEmpty(templateResourcePath)) {
if (null != templateDirectory) {
LOGGER.warn("Both templateDirectory and templateResourcePath were configured. templateResourcePath overwrites templateDirectory.");
}
configurator.setTemplateDir(templateResourcePath);
}
if (null != engine) { if (null != engine) {
configurator.setTemplatingEngineName(engine); configurator.setTemplatingEngineName(engine);
} }

View File

@ -161,14 +161,14 @@ public abstract class AbstractGenerator implements TemplatingGenerator {
if (StringUtils.isNotEmpty(library)) { if (StringUtils.isNotEmpty(library)) {
//look for the file in the library subfolder of the supplied template //look for the file in the library subfolder of the supplied template
final String libTemplateFile = buildLibraryFilePath(config.templateDir(), library, templateFile); final String libTemplateFile = buildLibraryFilePath(config.templateDir(), library, templateFile);
if (new File(libTemplateFile).exists()) { if (new File(libTemplateFile).exists() || this.getClass().getClassLoader().getResource(libTemplateFile) != null) {
return libTemplateFile; return libTemplateFile;
} }
} }
//check the supplied template main folder for the file //check the supplied template main folder for the file
final String template = config.templateDir() + File.separator + templateFile; final String template = config.templateDir() + File.separator + templateFile;
if (new File(template).exists()) { if (new File(template).exists() || this.getClass().getClassLoader().getResource(template) != null) {
return template; return template;
} }