All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 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