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

org.mule.maven.exchange.ExchangeModelProcessor Maven / Gradle / Ivy

/*
 * Copyright 2023 Salesforce, Inc. All rights reserved.
 */
package org.mule.maven.exchange;

import com.google.common.collect.Lists;
import org.apache.maven.model.Build;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginExecution;
import org.apache.maven.model.Repository;
import org.apache.maven.model.building.FileModelSource;
import org.apache.maven.model.building.ModelProcessor;
import org.apache.maven.model.building.ModelSource2;
import org.apache.maven.model.io.ModelParseException;
import org.apache.maven.model.io.ModelReader;
import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
import org.apache.maven.model.locator.ModelLocator;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.mule.maven.exchange.model.ExchangeDependency;
import org.mule.maven.exchange.model.ExchangeModel;
import org.mule.maven.exchange.model.ExchangeModelSerializer;
import org.mule.maven.exchange.utils.ApiProjectConstants;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

@Component(role = ModelProcessor.class)
public class ExchangeModelProcessor implements ModelProcessor {

    public static final String ORG_ID_KEY = "orgId";
    public static final String RAML_FRAGMENT = "raml-fragment";
    public static final String RULESET = "ruleset";
    public static final String ZIP_PACKAGING = "zip";
    public static final String LIGHT_RULESET = "light-ruleset";


    private static Logger LOGGER = Logger.getLogger(ExchangeModelProcessor.class.getName());

    private static final String EXCHANGE_JSON = "exchange.json";
    private static final String TEMPORAL_EXCHANGE_XML = ".exchange.xml";

    public static final String PACKAGER_VERSION = "2.5.2";

    public static final String MAVEN_FACADE_SYSTEM_PROPERTY = "-Dexchange.maven.repository.url";

    public static final String MAVEN_FACADE_V2_SYSTEM_PROPERTY = "-Dexchange.maven.v2.repository.url";

    /**
     * Enables transformation from 'ruleset' classifier with 'zip' type to 'light-ruleset' classifier.
     * Workarounds compatibility issues when 'ruleset' classifier is only available for 'yaml' type. [W-17152780]
     */
    public static final String MAVEN_USE_LIGHT_RULESETS_PROPERTY = "exchange.maven.dependencies.useLightRulesets";


    private ExchangeModelSerializer objectMapper = new ExchangeModelSerializer();

    @Requirement
    private ModelReader modelReader;

    @Requirement
    private ModelLocator modelLocator;

    public ExchangeModelProcessor() {
    }

    public void setModelReader(ModelReader modelReader) {
        this.modelReader = modelReader;
    }

    public void setModelLocator(ModelLocator modelLocator) {
        this.modelLocator = modelLocator;
    }

    @Override
    public File locatePom(File projectDirectory) {
        File pomFile = new File(projectDirectory, EXCHANGE_JSON);
        if (pomFile.exists()) {
            pomFile = new File(pomFile.getParentFile(), TEMPORAL_EXCHANGE_XML);
            try {
                pomFile.createNewFile();
                pomFile.deleteOnExit();
            } catch (IOException e) {
                throw new RuntimeException(String.format("error creating temporal `%s` empty file", TEMPORAL_EXCHANGE_XML), e);
            }
        } else {
            // behave like proper maven in case there is no pom from manager
            pomFile = modelLocator.locatePom(projectDirectory);
        }
        return pomFile;
    }

    @Override
    public Model read(File file, Map map) throws IOException, ModelParseException {
        return read(new FileInputStream(file), map);
    }

    @Override
    public Model read(InputStream inputStream, Map map) throws IOException, ModelParseException {
        return read(new InputStreamReader(inputStream, StandardCharsets.UTF_8), map);
    }

    @Override
    public Model read(Reader reader, Map options) throws IOException, ModelParseException {
        Object source = (options != null) ? options.get(SOURCE) : null;
        if (source instanceof ModelSource2 && ((ModelSource2) source).getLocation().endsWith(TEMPORAL_EXCHANGE_XML)) {
            // lookup the temporal file ".exchange.xml"
            final String temporalExchangeXml = ((ModelSource2) source).getLocation();
            final File temporaryExchangeXml = new File(temporalExchangeXml);
            final File exchangeJson = new File(temporaryExchangeXml.getParent(), EXCHANGE_JSON);

            // retrieve the original "exchange.json" file and obtain the Maven model
            final String exchangeJsonLocation = exchangeJson.getAbsolutePath();
            final FileInputStream exchangeJsonInputStream = new FileInputStream(exchangeJson);
            final Model mavenModel = getModel(exchangeJsonLocation, exchangeJsonInputStream);

            // store the reference from the original source of truth, the "exchange.json" file
            final FileModelSource temporalSourceXml = new FileModelSource(exchangeJson);
            ((Map) options).put(ModelProcessor.SOURCE, temporalSourceXml);

            // serialize the Maven model as XML in the temporal ".exchange.xml" file for proper installation of the .pom
            final String data = toXmlString(mavenModel);
            FileUtils.fileWrite(temporaryExchangeXml, data);
            mavenModel.setPomFile(temporaryExchangeXml);

            // done =]
            return mavenModel;
        } else {
            //It's a normal maven project with a pom.xml file
            //It's a normal maven project with a pom.xml file
            return modelReader.read(reader, options);
        }
    }

    /**
     * Helper method used by studio by reflection do not change the signature.
     *
     * @param exchangeJson
     * @return
     * @throws IOException
     */
    public static String toPomXml(File exchangeJson) throws IOException {
        final ExchangeModelProcessor exchangeModelProcessor = new ExchangeModelProcessor();
        final FileInputStream exchangeJsonInputStream = new FileInputStream(exchangeJson);
        final Model mavenModel = exchangeModelProcessor.getModel(exchangeJson.getAbsolutePath(), exchangeJsonInputStream);
        return exchangeModelProcessor.toXmlString(mavenModel);
    }

    private Model getModel(String location, InputStream inputStream) throws IOException {
        final ExchangeModel model = objectMapper.read(inputStream);

        boolean modified = false;
        if (StringUtils.isBlank(model.getAssetId())) {
            model.setAssetId(dasherize(model.getName()));
            modified = true;
        }
        if (StringUtils.isBlank(model.getVersion())) {
            model.setVersion("1.0.0-SNAPSHOT");
            modified = true;
        }
        if (StringUtils.isBlank(model.getGroupId())) {
            final String orgId = guessOrgId(location);
            if (orgId != null) {
                model.setGroupId(orgId);
                modified = true;
            } else {
                throw new RuntimeException("No `groupId` on exchange json or System property `groupId` or being an apivcs project");
            }
        }

        if (modified) {
            LOGGER.log(Level.WARNING, "[WARNING] exchange.json was modified by the build.");
            objectMapper.write(model, new File(location));
        }

        final Model mavenModel = toMavenModel(model);
        if (Boolean.getBoolean("exchange.maven.debug")) {
            System.out.println("Maven Model \n" + toXmlString(mavenModel));
        }
        return mavenModel;
    }

    private String guessOrgId(String location) {
        String groupId = System.getProperty("groupId");
        if (groupId == null) {
            final File projectFolder = new File(location).getParentFile();
            final File apiVcsConfigFile = new File(new File(projectFolder, ".apivcs"), "config.properties");
            if (apiVcsConfigFile.exists()) {
                final Properties properties = new Properties();
                try (final FileInputStream fileInputStream = new FileInputStream(apiVcsConfigFile)) {
                    properties.load(fileInputStream);
                } catch (IOException e) {

                }
                groupId = properties.getProperty(ORG_ID_KEY);
            }
        }

        return groupId;
    }

    public String toXmlString(Model mavenModel) throws IOException {
        StringWriter stringWriter = new StringWriter();
        new MavenXpp3Writer().write(stringWriter, mavenModel);
        return stringWriter.toString();
    }

    private Model toMavenModel(ExchangeModel model) {
        final Model result = new Model();
        result.setModelVersion("4.0.0");
        result.setArtifactId(model.getAssetId());
        result.setGroupId(model.getGroupId());
        result.setName(model.getName());
        result.setVersion(model.getVersion());
        result.setRepositories(Lists.newArrayList(createExchangeV3Repository(), createExchangeV2Repository(), createMulesoftReleasesRepository()));
        final List dependencies = model.getDependencies().stream().map(this::toMavenDependency).collect(Collectors.toList());
        result.setDependencies(dependencies);
        final Build build = new Build();
        build.setDirectory(String.format("${project.basedir}/%s/target", ApiProjectConstants.EXCHANGE_MODULES_TMP));
        build.setSourceDirectory("${project.basedir}");
        build.addPlugin(createPackagerPlugin(model));
        if (!model.getClassifier().equals(RAML_FRAGMENT)) {
            build.addPlugin(createConnectorInvokerPlugin("install"));
            build.addPlugin(createConnectorInvokerPlugin("deploy"));
        }
        result.setBuild(build);
        return result;
    }

    private Plugin createConnectorInvokerPlugin(String phase) {
        Plugin result = new Plugin();
        result.setGroupId("org.apache.maven.plugins");
        result.setArtifactId("maven-invoker-plugin");
        result.setVersion("3.2.0");
        final Xpp3Dom configuration = new Xpp3Dom("configuration");

        addSimpleNodeTo("goals", phase, configuration);
        addSimpleNodeTo("pom", String.format("${project.basedir}/%s/target/%s/pom.xml",
                ApiProjectConstants.EXCHANGE_MODULES_TMP,
                ApiProjectConstants.REST_CONNECT_OUTPUTDIR), configuration);
        boolean skipInvoker = Boolean.getBoolean(ApiProjectConstants.MAVEN_SKIP_REST_CONNECT);
        addSimpleNodeTo("skipInvocation", Boolean.toString(skipInvoker), configuration);

        // make the connector build a little bit faster by skipping docs and extension model generation
        final Xpp3Dom propertiesNode = new Xpp3Dom("properties");
        addSimpleNodeTo("skipDocumentation", "true", propertiesNode);
        addSimpleNodeTo("mule.maven.extension.model.disable", "true", propertiesNode);
        configuration.addChild(propertiesNode);

        result.setConfiguration(configuration);

        PluginExecution installConnector = new PluginExecution();
        installConnector.setId("rest-connect-" + phase);
        installConnector.setPhase(phase);
        installConnector.addGoal("run");
        result.addExecution(installConnector);

        return result;
    }

    private void addSimpleNodeTo(String nodeName, String valueNode, Xpp3Dom configuration) {
        final Xpp3Dom goalsNode = new Xpp3Dom(nodeName);
        goalsNode.setValue(valueNode);
        configuration.addChild(goalsNode);
    }

    private String dasherize(String name) {
        return name.toLowerCase().replaceAll(" ", "-");
    }

    private Plugin createPackagerPlugin(ExchangeModel model) {
        Plugin result = new Plugin();
        result.setGroupId("org.mule.maven.exchange");
        result.setArtifactId("exchange_api_packager");
        result.setVersion(PACKAGER_VERSION);
        final Xpp3Dom configuration = new Xpp3Dom("configuration");
        addSimpleNodeTo("classifier", model.getClassifier(), configuration);
        addSimpleNodeTo("mainFile", model.getMain(), configuration);
        result.setConfiguration(configuration);

        PluginExecution generateSources = new PluginExecution();
        generateSources.setId("generate-full-api");
        generateSources.setPhase("generate-sources");
        generateSources.addGoal("generate-full-api");
        result.addExecution(generateSources);

        PluginExecution compilePhase = new PluginExecution();
        compilePhase.setId("validate-api");
        compilePhase.setPhase("compile");
        compilePhase.addGoal("validate-api");
        result.addExecution(compilePhase);


        PluginExecution packagePhase = new PluginExecution();
        packagePhase.setId("generate-artifacts");
        packagePhase.setPhase("package");
        packagePhase.addGoal("package-api");
        packagePhase.addGoal("rest-connect");
        result.addExecution(packagePhase);
        return result;
    }

    private Dependency toMavenDependency(ExchangeDependency dep) {
        Dependency result = new Dependency();
        result.setArtifactId(dep.getAssetId());
        result.setGroupId(dep.getGroupId());
        result.setVersion(dep.getVersion());
        setOrDefault(dep.getPackaging(), ZIP_PACKAGING, result::setType);
        setOrDefault(dep.getClassifier(), null, this.setDependencyClassifier(result, result.getType()));
        return result;
    }

    private Consumer setDependencyClassifier(Dependency dependency, String packaging) {
        return (String classifier) -> dependency.setClassifier(this.transformClassifier(classifier, packaging));
    }

    private String transformClassifier(String classifier, String packaging) {
        if (useLightRulesetsEnabled() && classifier.equalsIgnoreCase(RULESET) && packaging.equalsIgnoreCase(ZIP_PACKAGING)) {
            return LIGHT_RULESET;
        }

        return classifier;
    }

    private void setOrDefault(Object obj, String fallback, Consumer fn) {
        if (obj instanceof String && !((String) obj).isEmpty())
            fn.accept((String) obj);
        else if (fallback != null) fn.accept(fallback);
    }

    private Repository createExchangeV3Repository() {
        String url = System.getProperty(MAVEN_FACADE_SYSTEM_PROPERTY, "https://maven.anypoint.mulesoft.com/api/v3/maven");
        Repository repository = new Repository();
        repository.setId("anypoint-exchange-v3");
        repository.setName("Anypoint Exchange");
        repository.setUrl(url);
        repository.setLayout("default");
        return repository;
    }

    private Repository createExchangeV2Repository() {
        String url = System.getProperty(MAVEN_FACADE_V2_SYSTEM_PROPERTY, "https://maven.anypoint.mulesoft.com/api/v2/maven");
        Repository repository = new Repository();
        repository.setId("anypoint-exchange-v2");
        repository.setName("Anypoint Exchange V2");
        repository.setUrl(url);
        repository.setLayout("default");
        return repository;
    }

    private Repository createMulesoftReleasesRepository() {
        Repository repository = new Repository();
        repository.setId("mulesoft-releases");
        repository.setName("Nexus Repository");
        repository.setUrl("https://repository-master.mulesoft.org/nexus/content/repositories/releases/");
        repository.setLayout("default");
        return repository;
    }

    private static boolean useLightRulesetsEnabled() {
        return Boolean.parseBoolean(System.getProperty(MAVEN_USE_LIGHT_RULESETS_PROPERTY, "false"));
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy