001/* 002 * JDrupes Builder 003 * Copyright (C) 2026 Michael N. Lipp 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <https://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.builder.mvnrepo; 020 021import java.io.File; 022import java.nio.file.Path; 023import java.util.Map; 024import java.util.Objects; 025import java.util.function.BiConsumer; 026import java.util.stream.Stream; 027import org.apache.maven.settings.Profile; 028import org.apache.maven.settings.Settings; 029import org.apache.maven.settings.building.DefaultSettingsBuilderFactory; 030import org.apache.maven.settings.building.DefaultSettingsBuildingRequest; 031import org.apache.maven.settings.building.SettingsBuilder; 032import org.apache.maven.settings.building.SettingsBuildingException; 033import org.apache.maven.settings.building.SettingsBuildingRequest; 034import org.apache.maven.settings.building.SettingsBuildingResult; 035import org.eclipse.aether.RepositorySystem; 036import org.eclipse.aether.RepositorySystemSession; 037import org.eclipse.aether.repository.RemoteRepository; 038import org.eclipse.aether.repository.RepositoryPolicy; 039import org.eclipse.aether.supplier.RepositorySystemSupplier; 040import org.eclipse.aether.supplier.SessionBuilderSupplier; 041import org.eclipse.aether.util.graph.transformer.ConfigurableVersionSelector; 042import org.jdrupes.builder.api.BuildException; 043 044/// Manages a global instance of [RepositorySystem] and 045/// [RepositorySystemSession] and provides lists of [RemoteRepository]s 046/// from profiles in `settings.xml`. 047/// 048public final class MavenContext { 049 050 @SuppressWarnings("PMD.AvoidUsingVolatile") 051 private static volatile SessionData theSession; 052 private static final RemoteRepository MAVEN_CENTRAL_REPO 053 = new RemoteRepository.Builder("central", "default", 054 "https://repo.maven.apache.org/maven2") 055 .setReleasePolicy( 056 createDefaultPolicy(MvnVersionType.RELEASE, true)) 057 .setSnapshotPolicy( 058 createDefaultPolicy(MvnVersionType.SNAPSHOT, false)) 059 .build(); 060 061 private MavenContext() { 062 } 063 064 private record SessionData(Settings settings, 065 RepositorySystem repositorySystem, 066 RepositorySystemSession repositorySession) { 067 } 068 069 /// Returns the singleton, lazily created session data. 070 /// 071 /// @return the session data 072 /// 073 @SuppressWarnings("PMD.AvoidSynchronizedStatement") 074 private static SessionData session() { 075 if (theSession != null) { 076 return theSession; 077 } 078 synchronized (MavenContext.class) { 079 if (theSession != null) { 080 return theSession; 081 } 082 return initSession(); 083 } 084 } 085 086 private static SessionData initSession() { 087 // Settings 088 SettingsBuildingRequest settingsRequest 089 = new DefaultSettingsBuildingRequest().setUserSettingsFile( 090 new File(System.getProperty("user.home"), ".m2/settings.xml")); 091 SettingsBuilder settingsBuilder 092 = new DefaultSettingsBuilderFactory().newInstance(); 093 SettingsBuildingResult settingsResult; 094 try { 095 settingsResult = settingsBuilder.build(settingsRequest); 096 } catch (SettingsBuildingException e) { 097 throw new BuildException().cause(e); 098 } 099 var settings = settingsResult.getEffectiveSettings(); 100 101 // Repository system 102 @SuppressWarnings("PMD.CloseResource") 103 var repoSystem = new RepositorySystemSupplier().get(); 104 105 // Repository system session 106 String localRepoPath = settings.getLocalRepository() != null 107 ? settings.getLocalRepository() 108 : System.getProperty("user.home") + "/.m2/repository"; 109 @SuppressWarnings("PMD.CloseResource") 110 var session = new SessionBuilderSupplier(repoSystem).get() 111 .withLocalRepositoryBaseDirectories(Path.of(localRepoPath)) 112 .setConfigProperty( 113 ConfigurableVersionSelector.CONFIG_PROP_SELECTION_STRATEGY, 114 ConfigurableVersionSelector.HIGHEST_SELECTION_STRATEGY) 115 .build(); 116 117 // Combine 118 theSession = new SessionData(settings, repoSystem, session); 119 return theSession; 120 } 121 122 /// Repository system. 123 /// 124 /// @return the repository system 125 /// 126 public static RepositorySystem repositorySystem() { 127 return session().repositorySystem(); 128 } 129 130 /// Repository session. 131 /// 132 /// @return the repository system session 133 /// 134 public static RepositorySystemSession repositorySession() { 135 return session().repositorySession(); 136 } 137 138 /// Looks up the credentials for the specified server in `settings.xml`. 139 /// Invokes the consumer with the username and password if found. 140 /// 141 /// @param serverId the server id 142 /// @param consumer the consumer 143 /// @return true, if found 144 /// 145 public static boolean lookupCredentials(String serverId, 146 BiConsumer<String, String> consumer) { 147 return session().settings().getServers().stream() 148 .filter(s -> serverId.equals(s.getId())).findFirst().map(s -> { 149 consumer.accept(s.getUsername(), s.getPassword()); 150 return true; 151 }).orElse(false); 152 } 153 154 /// Returns the [RemoteRepository] for Maven Central. 155 /// 156 /// @return the remote repository 157 /// 158 public static RemoteRepository mavenCentral() { 159 return MAVEN_CENTRAL_REPO; 160 } 161 162 /// Return the [RemoteRepository]s from the specified profile. 163 /// 164 /// @param profileId the profile id 165 /// @return the repositories 166 /// 167 public static Stream<RemoteRepository> repositories(String profileId) { 168 Objects.requireNonNull(profileId); 169 return repositories(session().settings(), profileId); 170 } 171 172 /// Return the [RemoteRepository]s from the specified settings and profile. 173 /// 174 /// @param settings the settings 175 /// @param profileId the profile id 176 /// @return the repositories 177 /// 178 public static Stream<RemoteRepository> repositories(Settings settings, 179 String profileId) { 180 Objects.requireNonNull(profileId); 181 if (!settings.getActiveProfiles().contains(profileId)) { 182 return Stream.empty(); 183 } 184 Map<String, Profile> profiles = settings.getProfilesAsMap(); 185 Profile profile = profiles.get(profileId); 186 if (profile == null) { 187 return Stream.empty(); 188 } 189 return profile.getRepositories().stream().map(repo -> { 190 var builder = new RemoteRepository.Builder(repo.getId(), 191 "default", repo.getUrl()) 192 .setReleasePolicy(createPolicy(MvnVersionType.RELEASE, 193 repo.getReleases())) 194 .setSnapshotPolicy(createPolicy(MvnVersionType.SNAPSHOT, 195 repo.getSnapshots())); 196 return builder.build(); 197 }); 198 } 199 200 /// Creates a policy for the specified type with reasonable defaults. 201 /// 202 /// @param type the type 203 /// @param enabled the enabled 204 /// @return the repository policy 205 /// 206 /* default */ static RepositoryPolicy createDefaultPolicy( 207 MvnVersionType type, boolean enabled) { 208 return createPolicy(type, enabled, null, null); 209 } 210 211 /// Creates a policy from settings data. Fills in reasonable defaults if 212 /// necessary. 213 /// 214 /// @param type the type 215 /// @param policy the policy data from settings 216 /// @return the repository policy 217 /// 218 /* default */ static RepositoryPolicy createPolicy(MvnVersionType type, 219 org.apache.maven.settings.RepositoryPolicy policy) { 220 if (policy == null) { 221 return createPolicy(type, false, null, null); 222 } 223 return createPolicy(type, policy.isEnabled(), 224 policy.getUpdatePolicy(), policy.getChecksumPolicy()); 225 } 226 227 /// Creates a policy from settings data with the given details. 228 /// Fills in reasonable defaults for `null` values. 229 /// 230 /// @param type the type 231 /// @param enabled the enabled 232 /// @param updatePolicy the update policy 233 /// @param checksumPolicy the checksum policy 234 /// @return the repository policy 235 /// 236 /* default */ static RepositoryPolicy createPolicy(MvnVersionType type, 237 boolean enabled, String updatePolicy, String checksumPolicy) { 238 if (updatePolicy == null) { 239 updatePolicy = type == MvnVersionType.SNAPSHOT 240 ? RepositoryPolicy.UPDATE_POLICY_ALWAYS 241 : RepositoryPolicy.UPDATE_POLICY_NEVER; 242 } 243 if (checksumPolicy == null) { 244 checksumPolicy = RepositoryPolicy.CHECKSUM_POLICY_WARN; 245 } 246 return new RepositoryPolicy(enabled, updatePolicy, checksumPolicy); 247 } 248}