io.automatiko.engine.codegen.process.OpenAPIClientGenerator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of automatiko-engine-codegen Show documentation
Show all versions of automatiko-engine-codegen Show documentation
The Code generation for Automatiko Engine
The newest version!
package io.automatiko.engine.codegen.process;
import static io.automatiko.engine.codegen.CodeGenConstants.MP_RESTCLIENT_PROP_ACCESS_TOKEN;
import static io.automatiko.engine.codegen.CodeGenConstants.MP_RESTCLIENT_PROP_AUTH_TYPE;
import static io.automatiko.engine.codegen.CodeGenConstants.MP_RESTCLIENT_PROP_BASIC;
import static io.automatiko.engine.codegen.CodeGenConstants.MP_RESTCLIENT_PROP_CLIENT_ID;
import static io.automatiko.engine.codegen.CodeGenConstants.MP_RESTCLIENT_PROP_CLIENT_SECRET;
import static io.automatiko.engine.codegen.CodeGenConstants.MP_RESTCLIENT_PROP_CUSTOM_NAME;
import static io.automatiko.engine.codegen.CodeGenConstants.MP_RESTCLIENT_PROP_CUSTOM_VALUE;
import static io.automatiko.engine.codegen.CodeGenConstants.MP_RESTCLIENT_PROP_ON_BEHALF_NAME;
import static io.automatiko.engine.codegen.CodeGenConstants.MP_RESTCLIENT_PROP_PASSWORD;
import static io.automatiko.engine.codegen.CodeGenConstants.MP_RESTCLIENT_PROP_REFRESH_TOKEN;
import static io.automatiko.engine.codegen.CodeGenConstants.MP_RESTCLIENT_PROP_REFRESH_URL;
import static io.automatiko.engine.codegen.CodeGenConstants.MP_RESTCLIENT_PROP_URL;
import static io.automatiko.engine.codegen.CodeGenConstants.MP_RESTCLIENT_PROP_USER;
import static org.openapitools.codegen.utils.StringUtils.camelize;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.openapitools.codegen.ClientOptInput;
import org.openapitools.codegen.CodegenOperation;
import org.openapitools.codegen.DefaultGenerator;
import org.openapitools.codegen.Generator;
import org.openapitools.codegen.api.TemplateProcessor;
import org.openapitools.codegen.api.TemplatingEngineAdapter;
import org.openapitools.codegen.api.TemplatingExecutor;
import org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen;
import org.openapitools.codegen.model.ModelMap;
import org.openapitools.codegen.model.OperationsMap;
import org.openapitools.codegen.templating.mustache.LowercaseLambda;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ast.expr.Name;
import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
import com.github.javaparser.ast.expr.StringLiteralExpr;
import io.automatiko.engine.api.definition.process.WorkflowProcess;
import io.automatiko.engine.codegen.GeneratorContext;
import io.automatiko.engine.codegen.ImportsOrganizer;
import io.automatiko.engine.codegen.di.DependencyInjectionAnnotator;
import io.automatiko.engine.services.utils.StringUtils;
import io.automatiko.engine.workflow.compiler.canonical.OpenAPIMetaData;
public class OpenAPIClientGenerator {
static {
System.setProperty("org.slf4j.simpleLogger.log.org.openapitools", "off");
}
private final String relativePath;
private GeneratorContext context;
private WorkflowProcess process;
private final String packageName;
private final String resourceClazzName;
private DependencyInjectionAnnotator annotator;
private OpenAPIMetaData openApiMetadata;
private Map generatedContent = new HashMap();
private Set usedTypes = new LinkedHashSet();
private final static String TEMP_PATH = System.getProperty("java.io.tmpdir");
private JavaJAXRSSpecServerCodegen codegen = new JavaJAXRSSpecServerCodegen() {
@Override
public String toApiName(String name) {
String computed = sanitizeName(openApiMetadata.name());
return camelize(computed);
}
@SuppressWarnings("unchecked")
@Override
public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List allModels) {
OperationsMap data = super.postProcessOperationsWithModels(objs, allModels);
Map operations = (Map) data.getOrDefault("operations", Collections.emptyMap());
Collection operation = (Collection) operations.getOrDefault("operation",
Collections.emptyMap());
Iterator it = operation.iterator();
while (it.hasNext()) {
CodegenOperation codegenOperation = (CodegenOperation) it.next();
if (!openApiMetadata.operations().contains(codegenOperation.operationId)) {
it.remove();
} else {
if (isServerlessWorkflow()) {
codegenOperation.allParams.forEach(p -> {
if (isServerlessWorkflow() && p.isBodyParam) {
p.dataType = "com.fasterxml.jackson.databind.JsonNode";
} else {
if (!context.getBuildContext()
.hasClassAvailable("io.automatiko.engine.app.rest.model." + p.dataType)) {
usedTypes.add("io.automatiko.engine.app.rest.model." + p.dataType);
}
}
});
codegenOperation.returnType = "com.fasterxml.jackson.databind.JsonNode";
} else {
codegenOperation.allParams.forEach(p -> {
if (!context.getBuildContext()
.hasClassAvailable("io.automatiko.engine.app.rest.model." + p.dataType)) {
usedTypes.add("io.automatiko.engine.app.rest.model." + p.dataType);
}
});
if (!context.getBuildContext()
.hasClassAvailable("io.automatiko.engine.app.rest.model." + codegenOperation.returnType)) {
usedTypes.add("io.automatiko.engine.app.rest.model." + codegenOperation.returnType);
}
if (openApiMetadata.hasParameter(codegenOperation.operationId, "mode", "async")) {
if (codegenOperation.returnType != null && !codegenOperation.returnType.equalsIgnoreCase("void")) {
codegenOperation.returnType = "io.smallrye.mutiny.Uni<" + codegenOperation.returnType + ">";
} else if (codegenOperation.returnType != null
&& codegenOperation.returnType.equalsIgnoreCase("void")) {
codegenOperation.returnType = "io.smallrye.mutiny.Uni";
}
}
}
}
}
return data;
}
@Override
public void postProcess() {
}
};
public OpenAPIClientGenerator(GeneratorContext context, WorkflowProcess process, OpenAPIMetaData openApiMetadata) {
this.context = context;
this.process = process;
this.openApiMetadata = openApiMetadata;
this.packageName = "io.automatiko.engine.app.rest";
this.resourceClazzName = StringUtils.capitalize(codegen.toApiName(openApiMetadata.name()));
this.relativePath = packageName.replace(".", "/") + "/" + resourceClazzName + ".java";
usedTypes.add(packageName + "." + resourceClazzName);
}
public OpenAPIClientGenerator withDependencyInjection(DependencyInjectionAnnotator annotator) {
this.annotator = annotator;
return this;
}
public String className() {
return resourceClazzName;
}
public String generatedFilePath() {
return relativePath;
}
protected boolean useInjection() {
return this.annotator != null;
}
public String generate() {
if (openApiMetadata.api().getServers() != null && !openApiMetadata.api().getServers().isEmpty()) {
context.setApplicationProperty(resourceClazzName.toLowerCase() + MP_RESTCLIENT_PROP_URL,
openApiMetadata.api().getServers().get(0).getUrl());
context.addInstruction("Set '" + resourceClazzName.toLowerCase() + MP_RESTCLIENT_PROP_URL
+ "' property to change defaut location (" + openApiMetadata.api().getServers().get(0).getUrl()
+ ") of the service");
context.addInstruction("In case authorization is required use following:");
context.addInstruction("For basic auth:");
context.addInstruction(" Set auth type via property '" + resourceClazzName.toLowerCase()
+ MP_RESTCLIENT_PROP_AUTH_TYPE + "' to 'basic'");
context.addInstruction(" Then one of the following:");
context.addInstruction(" Set user name and password with properties '" + resourceClazzName.toLowerCase()
+ MP_RESTCLIENT_PROP_USER + "', '" + resourceClazzName.toLowerCase() + MP_RESTCLIENT_PROP_PASSWORD + "'");
context.addInstruction(" Set base64 encoded username and password with property '"
+ resourceClazzName.toLowerCase() + MP_RESTCLIENT_PROP_BASIC + "'");
context.addInstruction("For OAuth2 auth:");
context.addInstruction(" Set auth type via property '" + resourceClazzName.toLowerCase()
+ MP_RESTCLIENT_PROP_AUTH_TYPE + "' to 'oauth'");
context.addInstruction(" Then depending on your OAuth configuration:");
context.addInstruction(" Set access token type via property '" + resourceClazzName.toLowerCase()
+ MP_RESTCLIENT_PROP_ACCESS_TOKEN);
context.addInstruction(
" Set client id type via property '" + resourceClazzName.toLowerCase() + MP_RESTCLIENT_PROP_CLIENT_ID);
context.addInstruction(" Set client secret type via property '" + resourceClazzName.toLowerCase()
+ MP_RESTCLIENT_PROP_CLIENT_SECRET);
context.addInstruction(" Set refresh token type via property '" + resourceClazzName.toLowerCase()
+ MP_RESTCLIENT_PROP_REFRESH_TOKEN);
context.addInstruction(" Set refresh url type via property '" + resourceClazzName.toLowerCase()
+ MP_RESTCLIENT_PROP_REFRESH_URL);
context.addInstruction("For custom (header) auth:");
context.addInstruction(" Set auth type via property '" + resourceClazzName.toLowerCase()
+ MP_RESTCLIENT_PROP_AUTH_TYPE + "' to 'custom'");
context.addInstruction(" Set custom auth header name with property '"
+ resourceClazzName.toLowerCase() + MP_RESTCLIENT_PROP_CUSTOM_NAME + "'");
context.addInstruction(" Set custom auth header value with property '"
+ resourceClazzName.toLowerCase() + MP_RESTCLIENT_PROP_CUSTOM_VALUE + "'");
context.addInstruction("For on behalf (propagated) auth:");
context.addInstruction(" Set auth type via property '" + resourceClazzName.toLowerCase()
+ MP_RESTCLIENT_PROP_AUTH_TYPE + "' to 'on-behalf'");
context.addInstruction(
" Set on behalf header name to be propagated (defaults to 'Authorization') with property '"
+ resourceClazzName.toLowerCase() + MP_RESTCLIENT_PROP_ON_BEHALF_NAME + "'");
}
codegen.setOutputDir(TEMP_PATH);
codegen.additionalProperties().put(JavaJAXRSSpecServerCodegen.INTERFACE_ONLY, true);
codegen.additionalProperties().put(JavaJAXRSSpecServerCodegen.USE_BEANVALIDATION, false);
codegen.additionalProperties().put(JavaJAXRSSpecServerCodegen.USE_SWAGGER_ANNOTATIONS, false);
codegen.additionalProperties().put(JavaJAXRSSpecServerCodegen.GENERATE_POM, false);
codegen.additionalProperties().put("lowercase", new LowercaseLambda());
codegen.setTemplateDir("open-api-templates");
codegen.setApiPackage("io.automatiko.engine.app.rest");
codegen.setModelPackage("io.automatiko.engine.app.rest.model");
codegen.setSourceFolder("");
ClientOptInput input = new ClientOptInput().openAPI(openApiMetadata.api()).config(codegen);
DefaultGenerator generator = new DefaultGenerator(false) {
private TemplatingEngineAdapter engineAdapter;
protected TemplatingEngineAdapter engineAdapter() {
return this.engineAdapter;
}
@Override
public Generator opts(ClientOptInput opts) {
this.setGenerateMetadata(false);
Generator generator = super.opts(opts);
engineAdapter = this.config.getTemplatingEngine();
TemplateProcessor delegate = templateProcessor;
this.templateProcessor = new TemplateProcessor() {
public File writeToFile(String filename, String contents) throws IOException {
if (filename.endsWith(".java")) {
// remove the absolute path prefix that is based on java tmp dir
String name = filename.substring(TEMP_PATH.length(), filename.lastIndexOf(".")).replaceAll("/",
".");
if (name.startsWith(".")) {
name = name.substring(1);
}
generatedContent.compute(name, (n, c) -> {
if (c == null) {
CompilationUnit unit = com.github.javaparser.StaticJavaParser
.parse(contents);
ClassOrInterfaceDeclaration template = unit.findFirst(ClassOrInterfaceDeclaration.class)
.get();
if (!isServerlessWorkflow() && unit.getPackageDeclaration().get().getNameAsString()
.equals("io.automatiko.engine.app.rest")) {
// add wildcard import to all model classes generated
unit.addImport("io.automatiko.engine.app.rest.model.*");
} else if (!isServerlessWorkflow() && unit.getPackageDeclaration().get().getNameAsString()
.equals("io.automatiko.engine.app.rest.model")) {
// find all import definitions that reference other model classes and add them to used types
unit.getImports().stream()
.filter(i -> i.getNameAsString()
.startsWith("io.automatiko.engine.app.rest.model"))
.forEach(i -> usedTypes.add(i.getNameAsString()));
}
// remove javax.annotation.Generated that is added by openapi tools generator
Optional generated = template.getAnnotationByName("Generated");
generated.ifPresent(an -> an.removeForced());
Optional p = template.getAnnotationByName("Path");
if (p.isPresent()) {
SingleMemberAnnotationExpr pathannotation = ((SingleMemberAnnotationExpr) p.get());
final String path = pathannotation.getMemberValue().asStringLiteralExpr().getValue();
template.findAll(MethodDeclaration.class).stream()
.filter(md -> !md.getNameAsString().equals("update") && md.getParentNode()
.filter(pn -> (pn instanceof ClassOrInterfaceDeclaration)
&& ((ClassOrInterfaceDeclaration) pn).getNameAsString()
.equals("HeaderConfig"))
.isEmpty())
.forEach(md -> {
Optional pathAnotation = md.getAnnotationByName("Path");
if (pathAnotation.isPresent()) {
String mpath = ((SingleMemberAnnotationExpr) pathAnotation.get())
.getMemberValue().asStringLiteralExpr().getValue();
((SingleMemberAnnotationExpr) pathAnotation.get())
.setMemberValue(
new StringLiteralExpr(
(path + mpath).replaceAll("//", "/")));
} else {
md.addAnnotation(new SingleMemberAnnotationExpr(new Name("Path"),
new StringLiteralExpr(path)));
}
});
pathannotation.setMemberValue(new StringLiteralExpr("/"));
}
ImportsOrganizer.organize(unit);
return unit.toString();
}
CompilationUnit unit = com.github.javaparser.StaticJavaParser.parse(c);
ClassOrInterfaceDeclaration template = unit.findFirst(ClassOrInterfaceDeclaration.class).get();
CompilationUnit newunit = com.github.javaparser.StaticJavaParser
.parse(contents);
ClassOrInterfaceDeclaration newtemplate = newunit.findFirst(ClassOrInterfaceDeclaration.class)
.get();
final String path = ((SingleMemberAnnotationExpr) newtemplate.getAnnotationByName("Path").get())
.getMemberValue().asStringLiteralExpr().getValue();
List declarations = newtemplate.findAll(MethodDeclaration.class,
md -> !md.getNameAsString().equals("update") && md.getParentNode()
.filter(p -> (p instanceof ClassOrInterfaceDeclaration)
&& ((ClassOrInterfaceDeclaration) p).getNameAsString()
.equals("HeaderConfig"))
.isEmpty());
declarations.stream().forEach(md -> {
MethodDeclaration cloned = md.clone();
Optional pathAnotation = cloned.getAnnotationByName("Path");
if (pathAnotation.isPresent()) {
String mpath = ((SingleMemberAnnotationExpr) pathAnotation.get())
.getMemberValue().asStringLiteralExpr().getValue();
((SingleMemberAnnotationExpr) pathAnotation.get())
.setMemberValue(new StringLiteralExpr((path + mpath).replaceAll("//", "/")));
} else {
cloned.addAnnotation(new SingleMemberAnnotationExpr(new Name("Path"),
new StringLiteralExpr(path)));
}
template.addMember(cloned);
});
ImportsOrganizer.organize(unit);
return unit.toString();
});
}
return new File(filename);
}
@Override
public File write(Map data, String template, File target) throws IOException {
if (engineAdapter().handlesFile(template)) {
// Only pass files with valid endings through template engine
String templateContent = engineAdapter().compileTemplate((TemplatingExecutor) delegate,
data, template);
return writeToFile(target.getPath(), templateContent);
} else {
return delegate.write(data, template, target);
}
}
@Override
public void skip(Path path, String context) {
delegate.skip(path, context);
}
@Override
public void ignore(Path path, String context) {
delegate.ignore(path, context);
}
@Override
public File writeToFile(String filename, byte[] contents) throws IOException {
return delegate.writeToFile(filename, contents);
}
};
return generator;
}
};
generator.opts(input).generate();
return null;
}
public Map generatedClasses() {
return generatedContent.entrySet().stream().filter(e -> usedTypes.contains(e.getKey()))
.collect(Collectors.toMap(k -> k.getKey(), v -> v.getValue()));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((resourceClazzName == null) ? 0 : resourceClazzName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
OpenAPIClientGenerator other = (OpenAPIClientGenerator) obj;
if (resourceClazzName == null) {
if (other.resourceClazzName != null)
return false;
} else if (!resourceClazzName.equals(other.resourceClazzName))
return false;
return true;
}
protected boolean isServerlessWorkflow() {
return ProcessCodegen.SUPPORTED_SW_EXTENSIONS.keySet().stream()
.filter(ext -> process.getResource() != null && process.getResource().getSourcePath().endsWith(ext)).findAny()
.isPresent();
}
}