001/*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2020-2024 Andres Almiray.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *     https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.kordamp.maven.checker.cli.internal;
019
020import com.google.common.base.CharMatcher;
021import com.google.common.collect.ImmutableMap;
022import org.apache.maven.execution.DefaultMavenExecutionRequest;
023import org.apache.maven.execution.MavenExecutionRequest;
024import org.apache.maven.project.MavenProject;
025import org.apache.maven.project.ProjectBuilder;
026import org.apache.maven.project.ProjectBuildingException;
027import org.apache.maven.project.ProjectBuildingRequest;
028import org.apache.maven.project.ProjectBuildingResult;
029import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
030import org.codehaus.plexus.ContainerConfiguration;
031import org.codehaus.plexus.DefaultContainerConfiguration;
032import org.codehaus.plexus.DefaultPlexusContainer;
033import org.codehaus.plexus.PlexusConstants;
034import org.codehaus.plexus.PlexusContainer;
035import org.codehaus.plexus.PlexusContainerException;
036import org.codehaus.plexus.classworlds.ClassWorld;
037import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
038import org.eclipse.aether.DefaultRepositorySystemSession;
039import org.eclipse.aether.RepositorySystem;
040import org.eclipse.aether.RepositorySystemSession;
041import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
042import org.eclipse.aether.impl.DefaultServiceLocator;
043import org.eclipse.aether.repository.LocalRepository;
044import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
045import org.eclipse.aether.spi.connector.transport.TransporterFactory;
046import org.eclipse.aether.transport.file.FileTransporterFactory;
047import org.eclipse.aether.transport.http.HttpTransporterFactory;
048
049import java.io.File;
050import java.io.IOException;
051import java.nio.file.Files;
052import java.nio.file.Path;
053import java.nio.file.Paths;
054import java.util.Locale;
055import java.util.Properties;
056
057/**
058 * @author Andres Almiray
059 * @since 1.1.0
060 */
061public class PomParser {
062    private static final CharMatcher LOWER_ALPHA_NUMERIC =
063        CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('0', '9'));
064
065    public static MavenProject createMavenProject(File pomFile) {
066        return createMavenProject(pomFile, createDefaultRepositorySystemSession(newRepositorySystem()));
067    }
068
069    private static MavenProject createMavenProject(File pomFile, RepositorySystemSession session) {
070        // MavenCli's way to instantiate PlexusContainer
071        ClassWorld classWorld =
072            new ClassWorld("plexus.core", Thread.currentThread().getContextClassLoader());
073        ContainerConfiguration containerConfiguration =
074            new DefaultContainerConfiguration()
075                .setClassWorld(classWorld)
076                .setRealm(classWorld.getClassRealm("plexus.core"))
077                .setClassPathScanning(PlexusConstants.SCANNING_INDEX)
078                .setAutoWiring(true)
079                .setJSR250Lifecycle(true)
080                .setName("pom-reader");
081        try {
082            PlexusContainer container = new DefaultPlexusContainer(containerConfiguration);
083
084            MavenExecutionRequest mavenExecutionRequest = new DefaultMavenExecutionRequest();
085            ProjectBuildingRequest projectBuildingRequest =
086                mavenExecutionRequest.getProjectBuildingRequest();
087
088            projectBuildingRequest.setRepositorySession(session);
089
090            // Profile activation needs properties such as JDK version
091            Properties properties = new Properties(); // allowing duplicate entries
092            properties.putAll(projectBuildingRequest.getSystemProperties());
093            properties.putAll(detectOsProperties());
094            properties.putAll(System.getProperties());
095            projectBuildingRequest.setSystemProperties(properties);
096
097            ProjectBuilder projectBuilder = container.lookup(ProjectBuilder.class);
098            ProjectBuildingResult projectBuildingResult =
099                projectBuilder.build(pomFile, projectBuildingRequest);
100            return projectBuildingResult.getProject();
101        } catch (PlexusContainerException | ComponentLookupException | ProjectBuildingException ex) {
102            throw new IllegalStateException(ex);
103        }
104    }
105
106    private static RepositorySystem newRepositorySystem() {
107        DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
108        locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);
109        locator.addService(TransporterFactory.class, FileTransporterFactory.class);
110        locator.addService(TransporterFactory.class, HttpTransporterFactory.class);
111
112        return locator.getService(RepositorySystem.class);
113    }
114
115    private static DefaultRepositorySystemSession createDefaultRepositorySystemSession(RepositorySystem system) {
116        DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
117        LocalRepository localRepository = new LocalRepository(findLocalRepository());
118        session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, localRepository));
119        return session;
120    }
121
122    private static String findLocalRepository() {
123        Path home = Paths.get(System.getProperty("user.home"));
124        Path localRepo = home.resolve(".m2").resolve("repository");
125        if (Files.isDirectory(localRepo)) {
126            return localRepo.toAbsolutePath().toString();
127        } else {
128            return makeTemporaryLocalRepository();
129        }
130    }
131
132    private static String makeTemporaryLocalRepository() {
133        try {
134            File temporaryDirectory = Files.createTempDirectory("m2").toFile();
135            temporaryDirectory.deleteOnExit();
136            return temporaryDirectory.getAbsolutePath();
137        } catch (IOException ex) {
138            return null;
139        }
140    }
141
142    public static ImmutableMap<String, String> detectOsProperties() {
143        return ImmutableMap.of(
144            "os.detected.name",
145            osDetectedName(),
146            "os.detected.arch",
147            osDetectedArch(),
148            "os.detected.classifier",
149            osDetectedName() + "-" + osDetectedArch());
150    }
151
152    private static String osDetectedName() {
153        String osNameNormalized =
154            LOWER_ALPHA_NUMERIC.retainFrom(System.getProperty("os.name").toLowerCase(Locale.ENGLISH));
155
156        if (osNameNormalized.startsWith("macosx") || osNameNormalized.startsWith("osx")) {
157            return "osx";
158        } else if (osNameNormalized.startsWith("windows")) {
159            return "windows";
160        }
161        // Since we only load the dependency graph, not actually use the
162        // dependency, it doesn't matter a great deal which one we pick.
163        return "linux";
164    }
165
166    private static String osDetectedArch() {
167        String osArchNormalized =
168            LOWER_ALPHA_NUMERIC.retainFrom(System.getProperty("os.arch").toLowerCase(Locale.ENGLISH));
169        switch (osArchNormalized) {
170            case "x8664":
171            case "amd64":
172            case "ia32e":
173            case "em64t":
174            case "x64":
175                return "x86_64";
176            default:
177                return "x86_32";
178        }
179    }
180}