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

org.openl.rules.maven.GenerateMojo Maven / Gradle / Ivy

The newest version!
package org.openl.rules.maven;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.helger.jcodemodel.AbstractJClass;
import com.helger.jcodemodel.EClassType;
import com.helger.jcodemodel.JCodeModel;
import com.helger.jcodemodel.JCodeModelException;
import com.helger.jcodemodel.JDefinedClass;
import com.helger.jcodemodel.JMethod;
import com.helger.jcodemodel.JMod;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;

import org.openl.CompiledOpenClass;
import org.openl.OpenClassUtil;
import org.openl.dependency.CompiledDependency;
import org.openl.dependency.DependencyType;
import org.openl.dependency.ResolvedDependency;
import org.openl.exception.OpenLCompilationException;
import org.openl.message.OpenLMessage;
import org.openl.message.OpenLMessagesUtils;
import org.openl.message.Severity;
import org.openl.rules.calc.CustomSpreadsheetResultOpenClass;
import org.openl.rules.calc.SpreadsheetResultOpenClass;
import org.openl.rules.lang.xls.binding.XlsModuleOpenClass;
import org.openl.rules.lang.xls.types.DatatypeOpenClass;
import org.openl.rules.project.instantiation.SimpleProjectEngineFactory;
import org.openl.rules.ruleservice.core.RuleServiceOpenLServiceInstantiationHelper;
import org.openl.rules.ruleservice.publish.common.MethodUtils;
import org.openl.syntax.code.Dependency;
import org.openl.syntax.impl.IdentifierNode;
import org.openl.types.IOpenClass;
import org.openl.types.IOpenField;
import org.openl.types.IOpenMember;
import org.openl.types.NullOpenClass;
import org.openl.util.CollectionUtils;
import org.openl.util.FileUtils;
import org.openl.util.StringUtils;

/**
 * Generates OpenL Tablets interface, domain classes, project descriptor, and unit tests.
 */
@Mojo(name = "generate", defaultPhase = LifecyclePhase.GENERATE_SOURCES, requiresDependencyResolution = ResolutionScope.COMPILE)
public final class GenerateMojo extends BaseOpenLMojo {

    @Parameter(defaultValue = "${project.compileClasspathElements}", readonly = true, required = true)
    private List classpath;

    @Parameter(defaultValue = "${project.compileSourceRoots}", readonly = true, required = true)
    private List sourceRoots;

    @Parameter(defaultValue = "${project.build.outputDirectory}", required = true, readonly = true)
    private String classesDirectory;

    /**
     * Output directory of the generated Java beans and OpenL Tablets Java interface.
     */
    @Parameter(defaultValue = "${project.build.directory}/generated-sources/openl")
    private File outputDirectory;

    /**
     * Comma-separated list of interfaces used for extending the generated interface.
     *
     * @since 5.19.1
     */
    @Parameter
    private String superInterface;

    /**
     * Generated Java interface from an OpenL Tablets project. If it is empty, generation is skipped.
     *
     * @since 5.19.1
     */
    @Parameter
    private String interfaceClass;

    /**
     * Rules module from which Java Beans and the interface is generated. Usually it corresponds to the Excel file name
     * without an extension. If this parameter is not defined, the whole project is used for generating Java classes.
     *
     * @since 5.23.2
     */
    @Parameter
    private String moduleName;

    /**
     * Parameter that adds the IRulesRuntimeContext arguments to the generated interface.
     *
     * @since 5.19.1
     */
    @Parameter
    private boolean isProvideRuntimeContext;

    /**
     * Parameter that adds additional methods to the generated interface to support variations.
     *
     * @since 5.19.1
     */
    @Parameter
    private boolean isProvideVariations;

    /**
     * Parameter for generating custom spreadsheet result bean classes.
     *
     * @since 5.23.0
     */
    @Parameter
    private boolean generateSpreadsheetResultBeans;

    /**
     * Additional options for compilation defined externally, such as external dependencies and overridden system
     * properties.
     */
    @Parameter
    private Map externalParameters;

    @Override
    public void execute(String sourcePath, boolean hasDependencies) throws Exception {
        if (outputDirectory.isDirectory()) {
            info("Cleaning up '", outputDirectory, "' directory...");
            FileUtils.delete(outputDirectory);
        }
        ClassLoader classLoader = null;
        try {
            classLoader = composeClassLoader();

            SimpleProjectEngineFactory.SimpleProjectEngineFactoryBuilder builder = new SimpleProjectEngineFactory.SimpleProjectEngineFactoryBuilder<>();
            if (hasDependencies) {
                builder.setWorkspace(workspaceFolder.getPath());
            }

            SimpleProjectEngineFactory factory = builder.setProject(sourcePath)
                    .setClassLoader(classLoader)
                    .setProvideRuntimeContext(isProvideRuntimeContext)
                    .setProvideVariations(isProvideVariations)
                    .setExecutionMode(true)
                    .setExternalParameters(externalParameters)
                    .build();

            CompiledOpenClass compiledOpenClass;
            // TODO Support project name
            if (StringUtils.isNotEmpty(moduleName) && interfaceClass == null) {
                try {
                    Collection resolvedDependencies = factory.getDependencyManager()
                            .resolveDependency(
                                    new Dependency(DependencyType.MODULE, new IdentifierNode(null, null, moduleName, null)),
                                    false);
                    CompiledDependency compiledDependency = factory.getDependencyManager()
                            .loadDependency(resolvedDependencies.iterator().next());
                    compiledOpenClass = compiledDependency.getCompiledOpenClass();
                } catch (OpenLCompilationException e) {
                    Collection messages = new LinkedHashSet<>();
                    for (OpenLMessage openLMessage : OpenLMessagesUtils.newErrorMessages(e)) {
                        String message = String
                                .format("Failed to load module '%s': %s", moduleName, openLMessage.getSummary());
                        messages.add(new OpenLMessage(message, Severity.ERROR));
                    }
                    ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
                    Thread.currentThread().setContextClassLoader(classLoader);
                    try {
                        compiledOpenClass = new CompiledOpenClass(NullOpenClass.the, messages);
                    } finally {
                        Thread.currentThread().setContextClassLoader(oldClassLoader);
                    }
                }
            } else {
                compiledOpenClass = factory.getCompiledOpenClass();
            }

            // Generate Java beans from OpenL dataTypes
            writeJavaBeans(compiledOpenClass.getTypes());

            if (generateSpreadsheetResultBeans) {
                if (compiledOpenClass.getOpenClass() instanceof XlsModuleOpenClass) {
                    writeCustomSpreadsheetResultBeans((XlsModuleOpenClass) compiledOpenClass.getOpenClass());
                }
            }

            // Generate interface is optional.
            if (interfaceClass != null) {
                Class interfaceClass = factory.getInterfaceClass();
                writeInterface(interfaceClass, factory.newInstance());
                project.addCompileSourceRoot(outputDirectory.getPath());
            }

        } finally {
            OpenClassUtil.releaseClassLoader(classLoader);
        }
    }

    private ClassLoader composeClassLoader() throws Exception {
        info("Composing the classloader for the following sources:");
        for (String dir : sourceRoots) {
            info("  # source roots > ", dir);
        }
        URL[] urls = toURLs(dependencyClasspath(classpath));
        return new URLClassLoader(urls, this.getClass().getClassLoader()) {
            @Override
            public Class findClass(String name) throws ClassNotFoundException {
                String className = name.replace('.', '/');
                String file = className.concat(".java");
                for (String dir : sourceRoots) {
                    if (new File(dir, file).isFile()) {
                        debug("  # FOUND > ", dir, "/", file);
                        byte[] bytes = generateStubClass(className);
                        return defineClass(name, bytes, 0, bytes.length);
                    }
                }
                debug("  > ", file);
                return super.findClass(name);
            }
        };
    }

    private List dependencyClasspath(List classpath) {
        List dependencyClasspath = new ArrayList<>(classpath);
        // No need to use target/classes folder in generate phase. Keep only classpath for dependency jars.
        dependencyClasspath.remove(classesDirectory);
        return dependencyClasspath;
    }

    @Override
    String getHeader() {
        return "OPENL JAVA SOURCES GENERATION";
    }

    private void writeJavaBeans(Collection types) throws IOException {
        if (CollectionUtils.isNotEmpty(types)) {
            for (IOpenClass openClass : types) {
                // Skip java code generation for types what is defined
                // thru DomainOpenClass (skip java code generation for alias
                // types, csr types).
                //
                if (openClass instanceof DatatypeOpenClass && ((DatatypeOpenClass) openClass).getBytecode() != null) {
                    Class datatypeClass = openClass.getInstanceClass();
                    String dataType = datatypeClass.getName();
                    info("Java Bean for Datatype: " + dataType);
                    Path filePath = Paths.get(classesDirectory, dataType.replace('.', '/') + ".class");
                    Files.createDirectories(filePath.getParent());
                    Files.write(filePath, ((DatatypeOpenClass) openClass).getBytecode());
                }
            }
        }
    }

    private void writeCustomSpreadsheetResultBeans(CustomSpreadsheetResultOpenClass customSpreadsheetResultOpenClass,
                                                   Set writtenSpreadsheetResultOpenClasses) throws IOException {
        if (!writtenSpreadsheetResultOpenClasses.contains(customSpreadsheetResultOpenClass)) {
            Class cls = customSpreadsheetResultOpenClass.getBeanClass();
            info("Java Bean for Spreadsheet Result: " + cls.getName());
            Path filePath = Paths.get(classesDirectory, cls.getName().replace('.', '/') + ".class");
            Files.createDirectories(filePath.getParent());
            Files.write(filePath, customSpreadsheetResultOpenClass.getBeanClassByteCode());
            writtenSpreadsheetResultOpenClasses.add(customSpreadsheetResultOpenClass);
            for (IOpenField openField : customSpreadsheetResultOpenClass.getFields()) {
                if (openField.getType() instanceof CustomSpreadsheetResultOpenClass) {
                    CustomSpreadsheetResultOpenClass customSpreadsheetResultOpenClass1 = (CustomSpreadsheetResultOpenClass) openField
                            .getType();
                    writeCustomSpreadsheetResultBeans(customSpreadsheetResultOpenClass1,
                            writtenSpreadsheetResultOpenClasses);
                } else if (openField.getType() instanceof SpreadsheetResultOpenClass) {
                    SpreadsheetResultOpenClass spreadsheetResultOpenClass = (SpreadsheetResultOpenClass) openField
                            .getType();
                    writeCustomSpreadsheetResultBeans(spreadsheetResultOpenClass.toCustomSpreadsheetResultOpenClass(),
                            writtenSpreadsheetResultOpenClasses);
                }
            }
        }
    }

    private void writeCustomSpreadsheetResultBeans(XlsModuleOpenClass xlsModuleOpenClass) throws IOException {
        if (xlsModuleOpenClass != null) {
            Set writtenSpreadsheetResultOpenClasses = new HashSet<>();
            for (IOpenClass openClass : xlsModuleOpenClass.getTypes()) {
                // Skip java code generation for other types
                if (openClass instanceof CustomSpreadsheetResultOpenClass) {
                    CustomSpreadsheetResultOpenClass customSpreadsheetResultOpenClass = (CustomSpreadsheetResultOpenClass) openClass;
                    writeCustomSpreadsheetResultBeans(customSpreadsheetResultOpenClass,
                            writtenSpreadsheetResultOpenClasses);
                }
            }
            if (xlsModuleOpenClass.getSpreadsheetResultOpenClassWithResolvedFieldTypes() != null) {
                writeCustomSpreadsheetResultBeans(
                        xlsModuleOpenClass.getSpreadsheetResultOpenClassWithResolvedFieldTypes()
                                .toCustomSpreadsheetResultOpenClass(),
                        writtenSpreadsheetResultOpenClasses);
            }
        }
    }

    private void writeInterface(Class clazz, Object service) throws IOException, JCodeModelException {
        info("Interface: " + interfaceClass);
        JCodeModel model = new JCodeModel();
        CodeHelper helper = new CodeHelper();

        // Generate a class body
        JDefinedClass java = model._class(interfaceClass, EClassType.INTERFACE);

        // Add super interfaces
        String[] interfaces = StringUtils.split(superInterface, ',');
        if (CollectionUtils.isNotEmpty(interfaces)) {
            for (String s : interfaces) {
                java._extends(helper.get(s));
            }
        }

        // Generate methods
        Method[] methods = clazz.getMethods();

        for (Method method : methods) {
            String name = method.getName();
            Class returnType = method.getReturnType();
            debug("   method: ", returnType, "   ", name, "()");
            JMethod jm = java.method(JMod.NONE, helper.get(returnType), name);
            IOpenMember openMember = RuleServiceOpenLServiceInstantiationHelper.getOpenMember(method, service);

            String[] argNames = MethodUtils
                    .getParameterNames(openMember, method, isProvideRuntimeContext, isProvideVariations);
            Class[] argTypes = method.getParameterTypes();
            for (int i = 0; i < argTypes.length; i++) {
                Class argType = argTypes[i];
                String argName = argNames[i];
                debug("      arg:     ", argName, "   ", argType);
                jm.param(helper.get(argType), argName);
            }
        }

        // Write the generated source code
        outputDirectory.mkdirs();
        model.build(outputDirectory, (PrintStream) null);
    }

    /**
     * A utility class to convert Java classes in CodeModel class descriptors. It is required for managing generated
     * beans because of they have not a classloader.
     */
    private static class CodeHelper {
        JCodeModel model = new JCodeModel();

        AbstractJClass get(Class clazz) throws JCodeModelException {
            if (clazz.isArray()) {
                Class componentType = clazz.getComponentType();
                AbstractJClass arrayType = get(componentType);
                return arrayType.array();
            }
            String clazzName = clazz.getName();
            EClassType eClassType = clazz.isInterface() ? EClassType.INTERFACE : EClassType.CLASS;
            return get(clazzName, eClassType);
        }

        AbstractJClass get(String clazzName) throws JCodeModelException {
            return get(clazzName, EClassType.INTERFACE);
        }

        private AbstractJClass get(String clazzName, EClassType eClassType) throws JCodeModelException {
            AbstractJClass jArgType = model._getClass(clazzName);
            if (jArgType == null) {
                jArgType = model._class(clazzName, eClassType);
            }
            return jArgType;
        }
    }

    private static byte[] generateStubClass(String className) {
            ClassWriter classWriter = new ClassWriter(0);
            classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, className, null, "java/lang/Object", null);
            classWriter.visitEnd();
            return classWriter.toByteArray();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy