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

org.apache.camel.maven.packaging.PackageLanguageMojo Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.camel.maven.packaging;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.inject.Inject;

import org.apache.camel.maven.packaging.generics.ClassUtil;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.annotations.Language;
import org.apache.camel.tooling.model.EipModel;
import org.apache.camel.tooling.model.EipModel.EipOptionModel;
import org.apache.camel.tooling.model.JsonMapper;
import org.apache.camel.tooling.model.LanguageModel;
import org.apache.camel.tooling.model.LanguageModel.LanguageOptionModel;
import org.apache.camel.tooling.model.SupportLevel;
import org.apache.camel.tooling.util.PackageHelper;
import org.apache.camel.tooling.util.Strings;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.codehaus.plexus.build.BuildContext;

import static java.lang.reflect.Modifier.isStatic;

/**
 * Analyses the Camel plugins in a project and generates extra descriptor information for easier auto-discovery in
 * Camel.
 */
@Mojo(name = "generate-languages-list", threadSafe = true)
public class PackageLanguageMojo extends AbstractGeneratorMojo {

    /**
     * The output directory for generated language resources
     */
    @Parameter(defaultValue = "${project.basedir}/src/generated/resources")
    protected File languageOutDir;

    /**
     * The output directory for generated schema resources
     */
    @Parameter(defaultValue = "${project.basedir}/src/generated/resources")
    protected File schemaOutDir;

    /**
     * The project build directory
     */
    @Parameter(defaultValue = "${project.build.directory}")
    protected File buildDir;

    @Inject
    public PackageLanguageMojo(Log log, MavenProject project, MavenProjectHelper projectHelper,
                               File buildDir, File languageOutDir, File schemaOutDir,
                               BuildContext buildContext) {
        super(projectHelper, buildContext);

        setLog(log);
        this.project = project;
        this.buildDir = buildDir;
        this.languageOutDir = languageOutDir;
        this.schemaOutDir = schemaOutDir;
    }

    /**
     * Execute goal.
     *
     * @throws org.apache.maven.plugin.MojoExecutionException execution of the main class or one of the threads it
     *                                                        generated failed.
     * @throws org.apache.maven.plugin.MojoFailureException   something bad happened...
     */
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        prepareLanguage();
    }

    public int prepareLanguage() throws MojoExecutionException {
        Log log = getLog();

        File camelMetaDir = new File(languageOutDir, "META-INF/services/org/apache/camel/");

        // first we need to setup the output directory because the next check
        // can stop the build before the end and eclipse always needs to know
        // about that directory
        if (projectHelper != null) {
            projectHelper.addResource(project, languageOutDir.getPath(), Collections.singletonList("**/language.properties"),
                    Collections.emptyList());
        }

        if (!haveResourcesChanged(log, project, buildContext, "META-INF/services/org/apache/camel/language")) {
            return 0;
        }

        Map javaTypes = new HashMap<>();

        StringBuilder buffer = new StringBuilder(256);
        int count = 0;

        File f = new File(project.getBasedir(), "target/classes");
        f = new File(f, "META-INF/services/org/apache/camel/language");
        if (f.exists() && f.isDirectory()) {
            File[] files = f.listFiles();
            if (files != null) {
                for (File file : files) {
                    String javaType = readClassFromCamelResource(file, buffer, buildContext);
                    if (!file.isDirectory() && file.getName().charAt(0) != '.') {
                        count++;
                    }
                    if (javaType != null) {
                        javaTypes.put(file.getName(), javaType);
                    }
                }
            }
        }

        // is this from Apache Camel then the data format is out of the box and
        // we should enrich the json schema with more details
        boolean apacheCamel = "org.apache.camel".equals(project.getGroupId());

        // find camel-core and grab the language model from there, and enrich
        // this model with information from this artifact
        // and create json schema model file for this language
        try {
            if (apacheCamel && count > 0) {
                File core = PackageHelper.findCamelCoreModelDirectory(project.getBasedir());
                if (core != null) {
                    for (Map.Entry entry : javaTypes.entrySet()) {
                        String name = entry.getKey();
                        Class javaType = loadClass(entry.getValue());
                        String modelName = asModelName(name);

                        String json = PackageHelper.loadText(new File(
                                core, "src/generated/resources/META-INF/org/apache/camel/model/language/" + modelName
                                      + PackageHelper.JSON_SUFIX));

                        LanguageModel languageModel = extractLanguageModel(project, json, name, javaType);
                        if (log.isDebugEnabled()) {
                            log.debug("Model: " + languageModel);
                        }

                        SchemaHelper.addModelMetadata(languageModel, project);
                        SchemaHelper.addModelMetadata(languageModel, javaType.getAnnotation(Metadata.class));

                        // build json schema for the language
                        String schema = JsonMapper.createParameterJsonSchema(languageModel);
                        if (log.isDebugEnabled()) {
                            log.debug("JSON schema\n" + schema);
                        }

                        // write this to the directory
                        Path out = schemaOutDir.toPath().resolve(schemaSubDirectory(languageModel.getJavaType()))
                                .resolve(name + PackageHelper.JSON_SUFIX);
                        updateResource(schemaOutDir.toPath(),
                                schemaSubDirectory(languageModel.getJavaType()) + "/" + name + PackageHelper.JSON_SUFIX,
                                schema);

                        if (log.isDebugEnabled()) {
                            log.debug("Generated " + out + " containing JSON schema for " + name + " language");
                        }
                    }
                } else {
                    throw new MojoExecutionException(
                            "Error finding core/camel-core/target/camel-core-model-" + project.getVersion()
                                                     + ".jar file. Make sure camel-core has been built first.");
                }
            }
        } catch (Exception e) {
            throw new MojoExecutionException("Error loading language model from camel-core. Reason: " + e, e);
        }

        if (count > 0) {
            String names = Stream.of(buffer.toString().split(" ")).sorted().collect(Collectors.joining(" "));
            String properties = createProperties(project, "languages", names);
            updateResource(camelMetaDir.toPath(), "language.properties", properties);
            log.info("Generated language.properties containing " + count + " Camel "
                     + (count > 1 ? "languages: " : "language: ") + names);
        } else {
            log.debug(
                    "No META-INF/services/org/apache/camel/language directory found. Are you sure you have created a Camel language?");
        }

        return count;
    }

    protected LanguageModel extractLanguageModel(MavenProject project, String json, String name, Class javaType) {
        EipModel def = JsonMapper.generateEipModel(json);
        LanguageModel model = new LanguageModel();
        model.setName(name);
        model.setTitle(asTitle(name, def.getTitle()));
        model.setDescription(asDescription(name, def.getDescription()));
        model.setFirstVersion(def.getFirstVersion());
        model.setLabel(def.getLabel());
        model.setDeprecated(def.isDeprecated());
        model.setDeprecationNote(def.getDeprecationNote());
        model.setDeprecatedSince(project.getProperties().getProperty("deprecatedSince"));
        model.setJavaType(javaType.getCanonicalName());
        model.setModelName(def.getName());
        model.setModelJavaType(def.getJavaType());
        model.setGroupId(project.getGroupId());
        model.setArtifactId(project.getArtifactId());
        model.setVersion(project.getVersion());

        // grab level from annotation, pom.xml or default to stable
        String level = project.getProperties().getProperty("supportLevel");
        boolean experimental = ClassUtil.hasAnnotation("org.apache.camel.Experimental", javaType);
        if (experimental) {
            model.setSupportLevel(SupportLevel.Experimental);
        } else if (level != null) {
            model.setSupportLevel(SupportLevel.safeValueOf(level));
        } else {
            model.setSupportLevel(SupportLevelHelper.defaultSupportLevel(model.getFirstVersion(), model.getVersion()));
        }

        for (EipOptionModel opt : def.getOptions()) {
            LanguageOptionModel option = new LanguageOptionModel();
            option.setName(opt.getName());
            option.setKind(opt.getKind());
            option.setDisplayName(opt.getDisplayName());
            option.setGroup(opt.getGroup());
            option.setLabel(opt.getLabel());
            option.setRequired(opt.isRequired());
            option.setType(opt.getType());
            option.setJavaType(opt.getJavaType());
            option.setEnums(opt.getEnums());
            option.setOneOfs(opt.getOneOfs());
            option.setPrefix(opt.getPrefix());
            option.setOptionalPrefix(opt.getOptionalPrefix());
            option.setMultiValue(opt.isMultiValue());
            option.setDeprecated(opt.isDeprecated());
            option.setDeprecationNote(opt.getDeprecationNote());
            option.setSecret(opt.isSecret());
            option.setDefaultValue(opt.getDefaultValue());
            option.setDefaultValueNote(opt.getDefaultValueNote());
            option.setAsPredicate(opt.isAsPredicate());
            option.setConfigurationClass(opt.getConfigurationClass());
            option.setConfigurationField(opt.getConfigurationField());
            option.setDescription(opt.getDescription());
            model.addOption(option);
        }

        // read class and find functionClass and add each field as a function in the model
        Class classElement = loadClass(javaType.getName());
        final Language lan = classElement.getAnnotation(Language.class);
        if (lan.functionsClass() != void.class) {
            classElement = loadClass(lan.functionsClass().getName());
            if (classElement != null) {
                for (Field field : classElement.getFields()) {
                    final boolean isEnum = classElement.isEnum();
                    if ((isEnum || isStatic(field.getModifiers()) && field.getType() == String.class)
                            && field.isAnnotationPresent(Metadata.class)) {
                        try {
                            addFunction(model, field);
                        } catch (Exception e) {
                            getLog().warn(e);
                        }
                    }
                }
            }
        }

        return model;
    }

    private void addFunction(LanguageModel model, Field field) throws Exception {
        final Class declaringClass = field.getDeclaringClass();
        final Metadata cm = declaringClass.getAnnotation(Metadata.class);
        String prefix = null;
        String suffix = null;
        if (cm != null && cm.annotations() != null) {
            for (String s : cm.annotations()) {
                prefix = Strings.after(s, "prefix=", prefix);
                suffix = Strings.after(s, "suffix=", suffix);
            }
        }
        final Metadata metadata = field.getAnnotation(Metadata.class);
        LanguageModel.LanguageFunctionModel fun = new LanguageModel.LanguageFunctionModel();
        fun.setConstantName(String.format("%s#%s", declaringClass.getName(), field.getName()));
        fun.setName((String) field.get(null));
        fun.setPrefix(prefix);
        fun.setSuffix(suffix);
        fun.setDescription(metadata.description().trim());
        fun.setKind("function");
        String displayName = metadata.displayName();
        // compute a display name if we don't have anything
        if (Strings.isNullOrEmpty(displayName)) {
            displayName = fun.getName();
            int pos = displayName.indexOf('(');
            if (pos == -1) {
                pos = displayName.indexOf(':');
            }
            if (pos == -1) {
                pos = displayName.indexOf('.');
            }
            if (pos != -1) {
                displayName = displayName.substring(0, pos);
            }
            displayName = displayName.replace('-', ' ');
            displayName = Strings.asTitle(displayName);
        }
        fun.setDisplayName(displayName);
        fun.setJavaType(metadata.javaType());
        fun.setRequired(metadata.required());
        fun.setDefaultValue(metadata.defaultValue());
        fun.setDeprecated(field.isAnnotationPresent(Deprecated.class));
        fun.setDeprecationNote(metadata.deprecationNote());
        fun.setSecret(metadata.secret());
        String label = metadata.label();
        boolean ognl = false;
        if (label.contains(",ognl")) {
            ognl = true;
            label = label.replace(",ognl", "");
        }
        String group = EndpointHelper.labelAsGroupName(label, false, false);
        fun.setGroup(group);
        fun.setLabel(label);
        fun.setOgnl(ognl);
        model.addFunction(fun);
    }

    private static String readClassFromCamelResource(File file, StringBuilder buffer, BuildContext buildContext)
            throws MojoExecutionException {
        // skip directories as there may be a sub .resolver directory such as in
        // camel-script
        if (file.isDirectory()) {
            return null;
        }
        String name = file.getName();
        if (name.charAt(0) != '.') {
            if (!buffer.isEmpty()) {
                buffer.append(" ");
            }
            buffer.append(name);
        }

        if (!buildContext.hasDelta(file)) {
            // if this file has not changed,
            // then no need to store the javatype
            // for the json file to be generated again
            // (but we do need the name above!)
            return null;
        }

        // find out the javaType for each data format
        try {
            String text = PackageHelper.loadText(file);
            Map map = PackageHelper.parseAsMap(text);
            return map.get("class");
        } catch (IOException e) {
            throw new MojoExecutionException("Failed to read file " + file + ". Reason: " + e, e);
        }
    }

    private static String asModelName(String name) {
        // special for some languages
        if ("bean".equals(name)) {
            return "method";
        } else if ("file".equals(name)) {
            return "simple";
        }
        return name;
    }

    private static String asTitle(String name, String title) {
        // special for some languages
        if ("file".equals(name)) {
            return "File";
        }
        return title;
    }

    private static String asDescription(String name, String description) {
        // special for some languages
        if ("file".equals(name)) {
            return "File related capabilities for the Simple language";
        }
        return description;
    }

    private static String schemaSubDirectory(String javaType) {
        int idx = javaType.lastIndexOf('.');
        String pckName = javaType.substring(0, idx);
        return "META-INF/" + pckName.replace('.', '/');
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy