PomInlinerTransformer.java

/*
 * 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.maven.internal.transformation.impl;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.xml.stream.XMLStreamException;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.maven.api.feature.Features;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.Interpolator;
import org.apache.maven.project.MavenProject;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.deployment.DeployRequest;
import org.eclipse.aether.installation.InstallRequest;

import static java.util.Objects.requireNonNull;

/**
 * Inliner POM transformer. The goal of this transformer is to fix Maven 3 issue about emitting (installing, deploying)
 * unusable POMs when using CI Friendly Versions.
 *
 * @since TBD
 */
@Singleton
@Named
class PomInlinerTransformer extends TransformerSupport {
    private final Interpolator interpolator;

    @Inject
    PomInlinerTransformer(Interpolator interpolator) {
        this.interpolator = requireNonNull(interpolator);
    }

    @Override
    public InstallRequest remapInstallArtifacts(RepositorySystemSession session, InstallRequest request) {
        return request.setArtifacts(replacePom(session, request.getArtifacts()));
    }

    @Override
    public DeployRequest remapDeployArtifacts(RepositorySystemSession session, DeployRequest request) {
        return request.setArtifacts(replacePom(session, request.getArtifacts()));
    }

    private Collection<Artifact> replacePom(RepositorySystemSession session, Collection<Artifact> artifacts) {
        Set<String> needsInlining = needsInlining(session);
        if (needsInlining.isEmpty()) {
            return artifacts;
        }
        ArrayList<Artifact> newArtifacts = new ArrayList<>(artifacts.size());
        for (Artifact artifact : artifacts) {
            if ("pom".equals(artifact.getExtension())
                    && artifact.getClassifier().isEmpty()) {
                try {
                    Path tmpPom = Files.createTempFile("pom-inliner-", ".xml");
                    String originalPom = Files.readString(artifact.getPath());
                    String interpolatedPom = interpolator.interpolate(
                            originalPom,
                            property -> {
                                if (needsInlining.contains(property)) {
                                    return (String)
                                            session.getConfigProperties().get(property);
                                }
                                return null;
                            },
                            false);
                    if (!Objects.equals(originalPom, interpolatedPom)) {
                        Files.writeString(tmpPom, interpolatedPom);
                        artifact = artifact.setPath(tmpPom);
                    }
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            }
            newArtifacts.add(artifact);
        }
        return newArtifacts;
    }

    @SuppressWarnings("unchecked")
    private Set<String> needsInlining(RepositorySystemSession session) {
        return (Set<String>) session.getData()
                .computeIfAbsent(
                        PomInlinerTransformer.class.getName() + ".needsInlining", ConcurrentHashMap::newKeySet);
    }

    @Override
    public void injectTransformedArtifacts(RepositorySystemSession session, MavenProject project) throws IOException {
        if (!Features.consumerPom(session.getConfigProperties())) {
            try {
                Model model = read(project.getFile().toPath());
                String version = model.getVersion();
                if (version == null && model.getParent() != null) {
                    version = model.getParent().getVersion();
                }
                String newVersion;
                if (version != null) {
                    HashSet<String> usedProperties = new HashSet<>();
                    newVersion = interpolator.interpolate(version.trim(), property -> {
                        if (!session.getConfigProperties().containsKey(property)) {
                            throw new IllegalArgumentException("Cannot inline property " + property);
                        }
                        usedProperties.add(property);
                        return (String) session.getConfigProperties().get(property);
                    });
                    if (!Objects.equals(version, newVersion)) {
                        needsInlining(session).addAll(usedProperties);
                    }
                }
            } catch (XMLStreamException e) {
                throw new IOException("Problem during inlining POM", e);
            }
        }
    }
}