fr.ird.observe.toolkit.maven.plugin.service.ServiceGenerateApiRunner Maven / Gradle / Ivy
package fr.ird.observe.toolkit.maven.plugin.service;
/*-
* #%L
* ObServe Toolkit :: Maven plugin
* %%
* Copyright (C) 2017 - 2022 Ultreia.io
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* .
* #L%
*/
import fr.ird.observe.security.Permission;
import fr.ird.observe.services.service.ObserveService;
import fr.ird.observe.spi.MethodCredential;
import fr.ird.observe.spi.ProjectPackagesDefinition;
import fr.ird.observe.spi.ServiceInternal;
import fr.ird.observe.spi.navigation.model.MetaModel;
import fr.ird.observe.spi.navigation.model.MetaModelNode;
import fr.ird.observe.spi.navigation.model.MetaModelNodeType;
import fr.ird.observe.spi.navigation.model.MetaModelSupport;
import fr.ird.observe.spi.navigation.model.tree.TreeProjectModelBuilder;
import fr.ird.observe.toolkit.maven.plugin.MojoRunnable;
import io.ultreia.java4all.http.maven.plugin.HttpMojoSupport;
import io.ultreia.java4all.http.maven.plugin.model.ImportManager;
import io.ultreia.java4all.http.maven.plugin.model.MethodDescription;
import io.ultreia.java4all.http.spi.Delete;
import io.ultreia.java4all.http.spi.Get;
import io.ultreia.java4all.http.spi.Head;
import io.ultreia.java4all.http.spi.Patch;
import io.ultreia.java4all.http.spi.Post;
import io.ultreia.java4all.http.spi.Put;
import io.ultreia.java4all.http.spi.Trace;
import io.ultreia.java4all.http.spi.Write;
import io.ultreia.java4all.lang.Strings;
import org.apache.maven.plugin.logging.Log;
import org.nuiton.eugene.GeneratorUtil;
import org.nuiton.eugene.java.extension.ImportsManager;
import org.reflections.Reflections;
import javax.annotation.processing.Generated;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Created on 21/01/2022.
*
* @author Tony Chemit - [email protected]
* @since 5.0.64
*/
//FIXME Improve this mechanism
public class ServiceGenerateApiRunner extends MojoRunnable {
public static final String TEMPLATE = "\n" +
"package %1$s;\n\n" +
"%2$s\n" +
"@Generated(value = \"%3$s\", date = \"%4$s\")\n" +
"public interface %5$s extends %6$s {\n\n" +
"%7$s\n" +
"}";
@SuppressWarnings("SpellCheckingInspection")
public static final String METHOD = " %1$spublic %2$s %3$s(%4$s)%5$s;\n\n";
public final Map> handlers = Map.of(
MetaModelNodeType.ReferentialType, this::processReferential,
MetaModelNodeType.RootOpen, this::processRootOpen,
MetaModelNodeType.Open, this::processOpen,
MetaModelNodeType.Edit, this::processEdit,
MetaModelNodeType.Table, this::processTable,
MetaModelNodeType.Simple, this::processSimple
);
private Path sourceDirectory;
private Path targetDirectory;
public static List> getAllServices(Class> serviceTypeClass) {
return new Reflections(serviceTypeClass.getPackage().getName()).getSubTypesOf(serviceTypeClass).stream().filter(Class::isInterface).filter(c -> c.isAnnotationPresent(ServiceInternal.class)).sorted(Comparator.comparing(Class::getName)).collect(Collectors.toList());
}
public static String importAndSimplify(ImportsManager manager, String imports) {
imports = Objects.requireNonNull(imports).trim();
// remove ... operator
if (imports.endsWith("...")) {
imports = imports.substring(0, imports.length() - 3);
return importAndSimplify(manager, imports) + "...";
}
Set typesList = GeneratorUtil.getTypesList(imports);
if (typesList.size() > 1) {
// Can't simplify in this case (need to rebuild the hole thing, not now...)
typesList.forEach(manager::addImport);
return imports;
}
String oneType = typesList.iterator().next();
return manager.importAndSimplify(oneType);
}
public void setTargetDirectory(Path targetDirectory) {
this.targetDirectory = targetDirectory;
}
public void setSourceDirectory(Path sourceDirectory) {
this.sourceDirectory = sourceDirectory;
}
@Override
public void init() {
super.init();
Objects.requireNonNull(targetDirectory);
Objects.requireNonNull(sourceDirectory);
}
@Override
public void run() {
getLog().info(String.format("Will generate at %s", targetDirectory));
List> allServices = getAllServices(ObserveService.class);
ProjectPackagesDefinition definition = ProjectPackagesDefinition.of(Thread.currentThread().getContextClassLoader());
MetaModelSupport descriptor = new MetaModelSupport(Thread.currentThread().getContextClassLoader(), definition.getName());
MetaModel model = descriptor.getModel();
Map nodes = model.getNodes();
for (Class> service : allServices) {
ServiceInternal annotation = Objects.requireNonNull(service.getAnnotation(ServiceInternal.class));
List genericTypes = List.of(annotation.value());
MetaModelNodeType type = annotation.type();
Function function = handlers.get(type);
if (function == null) {
throw new IllegalStateException(String.format("Can't manage type: %s (available: %s)", type, handlers.keySet()));
}
List> nodesForService = nodes.entrySet().stream().filter(e -> e.getValue().getNodeType() == type).collect(Collectors.toList());
Context context = new Context(isVerbose(), getLog(), targetDirectory, service, genericTypes, nodesForService);
getLog().info(String.format("Will generate service %s, using annotation: %s, for %d method(s) on %d type()s.", context.fullyQualifiedName, genericTypes, context.methods.size(), nodesForService.size()));
String content = function.apply(context);
if (content == null) {
continue;
}
Path targetFile = context.targetFile;
try {
store(targetFile, content);
} catch (IOException e) {
throw new IllegalStateException(String.format("Can't generate %s", targetFile));
}
}
}
protected String processReferential(Context context) {
return context.toContent();
}
protected String processEdit(Context context) {
return context.toContent();
}
protected String processRootOpen(Context context) {
return context.toContent();
}
protected String processOpen(Context context) {
return context.toContent();
}
protected String processTable(Context context) {
return context.toContent();
}
protected String processSimple(Context context) {
return context.toContent();
}
class Context {
final Class> serviceType;
final List genericTypes;
final String servicePackageName;
final String serviceSimpleName;
final String fullyQualifiedName;
final List> nodesForService;
final Map genericMapping;
final ImportManager importManager;
final Set methods;
final Path targetFile;
private final Log log;
private final ImportsManager realImportManager;
private final List methodsContent;
public Context(boolean verbose, Log log, Path targetDirectory, Class> serviceType, List genericTypes, List> nodesForService) {
this.log = log;
this.serviceType = serviceType;
this.genericTypes = genericTypes;
this.servicePackageName = serviceType.getPackage().getName().replace(".internal", "");
this.serviceSimpleName = serviceType.getSimpleName();
this.fullyQualifiedName = servicePackageName + "." + serviceSimpleName;
this.nodesForService = nodesForService;
this.genericMapping = HttpMojoSupport.genericMapping(log, ObserveService.class, serviceType);
Path targetPackage = targetDirectory.resolve(servicePackageName.replaceAll("\\.", "/"));
this.targetFile = targetPackage.resolve(serviceSimpleName + ".java");
this.importManager = new ImportManager();
methods = new LinkedHashSet<>();
MethodDescription.createMethodDescriptions(verbose ? log : null, ObserveService.class, serviceType, methods, method -> new ServiceLocalMethodDescriptionImpl(importManager, serviceType, method, genericMapping));
realImportManager = new ImportsManager();
methodsContent = new LinkedList<>();
realImportManager.addImport(Generated.class);
}
protected void addMethod(ServiceLocalMethodDescriptionImpl methodDescription, List methods) {
Permission methodeCredentials = methodDescription.getMethodeCredentials();
StringBuilder annotationsBuilder = new StringBuilder();
Method method = methodDescription.getMethod();
Annotation[] declaredAnnotations = method.getDeclaredAnnotations();
for (Annotation annotation : declaredAnnotations) {
Class extends Annotation> annotationClass = annotation.annotationType();
String annotationType = realImportManager.importAndSimplify(annotationClass.getName());
annotationsBuilder.append("\n @").append(annotationType);
String parameters = "";
//noinspection StatementWithEmptyBody
if (annotation instanceof Write) {
} else if (annotation instanceof MethodCredential) {
parameters += ", " + Permission.class.getSimpleName() + "." + methodeCredentials.name();
} else if (annotation instanceof Get) {
Get annotation1 = (Get) annotation;
boolean addAuthToken = annotation1.addAuthtoken();
boolean useMultiPartForm = annotation1.useMultiPartForm();
int timeOut = annotation1.timeOut();
if (!addAuthToken) {
parameters += ", addAuthtoken = false";
}
if (useMultiPartForm) {
parameters += ", useMultiPartForm = true";
}
if (timeOut != 1) {
parameters += ", timeOut = " + timeOut;
}
} else if (annotation instanceof Post) {
Post annotation1 = (Post) annotation;
boolean addAuthToken = annotation1.addAuthtoken();
boolean useMultiPartForm = annotation1.useMultiPartForm();
int timeOut = annotation1.timeOut();
if (!addAuthToken) {
parameters += ", addAuthtoken = false";
}
if (useMultiPartForm) {
parameters += ", useMultiPartForm = true";
}
if (timeOut != 1) {
parameters += ", timeOut = " + timeOut;
}
} else if (annotation instanceof Put) {
Put annotation1 = (Put) annotation;
boolean addAuthToken = annotation1.addAuthtoken();
boolean useMultiPartForm = annotation1.useMultiPartForm();
int timeOut = annotation1.timeOut();
if (!addAuthToken) {
parameters += ", addAuthtoken = false";
}
if (useMultiPartForm) {
parameters += ", useMultiPartForm = true";
}
if (timeOut != 1) {
parameters += ", timeOut = " + timeOut;
}
} else if (annotation instanceof Delete) {
Delete annotation1 = (Delete) annotation;
boolean addAuthToken = annotation1.addAuthtoken();
boolean useMultiPartForm = annotation1.useMultiPartForm();
int timeOut = annotation1.timeOut();
if (!addAuthToken) {
parameters += ", addAuthtoken = false";
}
if (useMultiPartForm) {
parameters += ", useMultiPartForm = true";
}
if (timeOut != 1) {
parameters += ", timeOut = " + timeOut;
}
} else if (annotation instanceof Patch) {
Patch annotation1 = (Patch) annotation;
boolean addAuthToken = annotation1.addAuthtoken();
boolean useMultiPartForm = annotation1.useMultiPartForm();
int timeOut = annotation1.timeOut();
if (!addAuthToken) {
parameters += ", addAuthtoken = false";
}
if (useMultiPartForm) {
parameters += ", useMultiPartForm = true";
}
if (timeOut != 1) {
parameters += ", timeOut = " + timeOut;
}
} else if (annotation instanceof Head) {
Head annotation1 = (Head) annotation;
boolean addAuthToken = annotation1.addAuthtoken();
boolean useMultiPartForm = annotation1.useMultiPartForm();
int timeOut = annotation1.timeOut();
if (!addAuthToken) {
parameters += ", addAuthtoken = false";
}
if (useMultiPartForm) {
parameters += ", useMultiPartForm = true";
}
if (timeOut != 1) {
parameters += ", timeOut = " + timeOut;
}
} else if (annotation instanceof Trace) {
Trace annotation1 = (Trace) annotation;
boolean addAuthToken = annotation1.addAuthtoken();
boolean useMultiPartForm = annotation1.useMultiPartForm();
int timeOut = annotation1.timeOut();
if (!addAuthToken) {
parameters += ", addAuthtoken = false";
}
if (useMultiPartForm) {
parameters += ", useMultiPartForm = true";
}
if (timeOut != 1) {
parameters += ", timeOut = " + timeOut;
}
}
if (parameters.length() > 0) {
annotationsBuilder.append("(").append(parameters.substring(2)).append(")");
}
}
String annotations = annotationsBuilder.length() == 0 ? "" : (annotationsBuilder.substring(5) + "\n ");
String methodName = methodDescription.getName();
String genericDefinitionOfReturnType = importManager.getGenericDefinitionOfReturnType(method);
// String returnType = importAndSimplify(realImportManager, methodDescription.getReturnType().substring(genericDefinitionOfReturnType.length()));
String returnType = realImportManager.importAndSimplify(methodDescription.getReturnType().substring(genericDefinitionOfReturnType.length()));
StringBuilder exceptionsBuilder = new StringBuilder();
for (Class> exception : methodDescription.getExceptions()) {
exceptionsBuilder.append(", ").append(importAndSimplify(realImportManager, exception.getName()));
}
String exceptions = exceptionsBuilder.length() == 0 ? "" : " throws " + exceptionsBuilder.substring(2);
realImportManager.importAndSimplify(Permission.class.getName());
for (Map.Entry entry : nodesForService) {
MetaModelNode node = entry.getValue();
String dataType = node.getDataType();
String nodeReturnType = returnType;
if (genericTypes.contains(returnType)) {
switch (returnType) {
case "D":
nodeReturnType = "fr.ird.observe.dto." + dataType + "Dto";
break;
case "R":
nodeReturnType = "fr.ird.observe.dto." + dataType + "Reference";
break;
default:
throw new IllegalStateException("Can't manage this generic type: " + returnType);
}
} else {
if (genericTypes.contains("D")) {
nodeReturnType = nodeReturnType.replace("", "");
}
if (genericTypes.contains("R")) {
nodeReturnType = nodeReturnType.replace("", "");
}
}
StringBuilder parametersDeclarationBuilder = new StringBuilder();
int index = 0;
for (String parameterName : methodDescription.getParameterNames()) {
String parameterType = methodDescription.getParameterTypes().get(index++);
if (parameterType.contains("Class<")) {
continue;
}
if (genericTypes.contains(parameterType)) {
switch (parameterType) {
case "D":
parameterType = "fr.ird.observe.dto." + dataType + "Dto";
break;
case "R":
parameterType = "fr.ird.observe.dto." + dataType + "Reference";
break;
default:
throw new IllegalStateException("Can't manage this generic type: " + parameterType);
}
} else if (!parameterType.endsWith("[]")) {
parameterType = importAndSimplify(realImportManager, parameterType);
if (genericTypes.contains("D")) {
parameterType = parameterType.replace("", "");
}
if (genericTypes.contains("R")) {
parameterType = parameterType.replace("", "");
}
}
parametersDeclarationBuilder.append(", ").append(parameterType).append(" ").append(parameterName);
}
String parametersDeclaration = parametersDeclarationBuilder.length() == 0 ? "" : parametersDeclarationBuilder.substring(2);
String nodeMethodPrefix = TreeProjectModelBuilder.toCamelCase(ProjectPackagesDefinition.removeFirstPackage(dataType));
String nodeMethodName = nodeMethodPrefix + Strings.capitalize(methodName);
String methodContent = String.format(METHOD, annotations, nodeReturnType, nodeMethodName, parametersDeclaration, exceptions);
methodsContent.add(methodContent);
}
}
public String toContent() {
realImportManager.addExcludedPattern(".+\\." + fullyQualifiedName);
log.info(String.format("will generate %d method(s) for %s", methods.size(), fullyQualifiedName));
for (ServiceLocalMethodDescriptionImpl methodDescription : methods) {
addMethod(methodDescription, methodsContent);
}
importManager.toDescription().forEach(t -> realImportManager.addImport(t.getName()));
List imports = realImportManager.getImports(servicePackageName).stream().map(s -> "import " + s + ";\n").collect(Collectors.toList());
return String.format(TEMPLATE,
servicePackageName,
String.join("", imports),
ServiceGenerateApiRunner.this.getClass().getName(),
new Date(),
serviceSimpleName,
serviceType.getName(),
String.join("", methodsContent));
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy