1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.internal.impl;
20
21 import javax.xml.stream.Location;
22 import javax.xml.stream.XMLStreamException;
23
24 import java.io.File;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.function.Function;
33 import java.util.function.Supplier;
34
35 import org.apache.maven.api.di.Inject;
36 import org.apache.maven.api.di.Named;
37 import org.apache.maven.api.services.BuilderProblem;
38 import org.apache.maven.api.services.Interpolator;
39 import org.apache.maven.api.services.SettingsBuilder;
40 import org.apache.maven.api.services.SettingsBuilderException;
41 import org.apache.maven.api.services.SettingsBuilderRequest;
42 import org.apache.maven.api.services.SettingsBuilderResult;
43 import org.apache.maven.api.services.Source;
44 import org.apache.maven.api.services.xml.SettingsXmlFactory;
45 import org.apache.maven.api.services.xml.XmlReaderException;
46 import org.apache.maven.api.services.xml.XmlReaderRequest;
47 import org.apache.maven.api.settings.Activation;
48 import org.apache.maven.api.settings.Profile;
49 import org.apache.maven.api.settings.Repository;
50 import org.apache.maven.api.settings.RepositoryPolicy;
51 import org.apache.maven.api.settings.Server;
52 import org.apache.maven.api.settings.Settings;
53 import org.apache.maven.internal.impl.model.DefaultInterpolator;
54 import org.apache.maven.settings.v4.SettingsMerger;
55 import org.apache.maven.settings.v4.SettingsTransformer;
56 import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
57
58
59
60
61
62 @Named
63 public class DefaultSettingsBuilder implements SettingsBuilder {
64
65 private final DefaultSettingsValidator settingsValidator = new DefaultSettingsValidator();
66
67 private final SettingsMerger settingsMerger = new SettingsMerger();
68
69 private final SettingsXmlFactory settingsXmlFactory;
70
71 private final Interpolator interpolator;
72
73 private final SecDispatcher secDispatcher;
74
75
76
77
78
79 public DefaultSettingsBuilder() {
80 this(new DefaultSettingsXmlFactory(), new DefaultInterpolator(), null);
81 }
82
83
84
85
86 @Inject
87 public DefaultSettingsBuilder(
88 SettingsXmlFactory settingsXmlFactory, Interpolator interpolator, SecDispatcher secDispatcher) {
89 this.settingsXmlFactory = settingsXmlFactory;
90 this.interpolator = interpolator;
91 this.secDispatcher = secDispatcher;
92 }
93
94 @Override
95 public SettingsBuilderResult build(SettingsBuilderRequest request) throws SettingsBuilderException {
96 List<BuilderProblem> problems = new ArrayList<>();
97
98 Source installationSource = request.getInstallationSettingsSource().orElse(null);
99 Settings installation = readSettings(installationSource, false, request, problems);
100
101 Source projectSource = request.getProjectSettingsSource().orElse(null);
102 Settings project = readSettings(projectSource, true, request, problems);
103
104 Source userSource = request.getUserSettingsSource().orElse(null);
105 Settings user = readSettings(userSource, false, request, problems);
106
107 Settings effective =
108 settingsMerger.merge(user, settingsMerger.merge(project, installation, false, null), false, null);
109
110
111
112
113 if (effective.getRepositories().isEmpty()
114 && effective.getPluginRepositories().isEmpty()) {
115 Repository central = Repository.newBuilder()
116 .id("central")
117 .name("Central Repository")
118 .url("https://repo.maven.apache.org/maven2")
119 .snapshots(RepositoryPolicy.newBuilder().enabled(false).build())
120 .build();
121 Repository centralWithNoUpdate = central.withReleases(
122 RepositoryPolicy.newBuilder().updatePolicy("never").build());
123 effective = Settings.newBuilder(effective)
124 .repositories(List.of(central))
125 .pluginRepositories(List.of(centralWithNoUpdate))
126 .build();
127 }
128
129
130 String localRepository = effective.getLocalRepository();
131 if (localRepository != null && !localRepository.isEmpty()) {
132 Path file = Paths.get(localRepository);
133 if (!file.isAbsolute() && file.toString().startsWith(File.separator)) {
134 effective = effective.withLocalRepository(file.toAbsolutePath().toString());
135 }
136 }
137
138 if (hasErrors(problems)) {
139 throw new SettingsBuilderException("Error building settings", problems);
140 }
141
142 return new DefaultSettingsBuilderResult(effective, problems);
143 }
144
145 private boolean hasErrors(List<BuilderProblem> problems) {
146 if (problems != null) {
147 for (BuilderProblem problem : problems) {
148 if (BuilderProblem.Severity.ERROR.compareTo(problem.getSeverity()) >= 0) {
149 return true;
150 }
151 }
152 }
153
154 return false;
155 }
156
157 private Settings readSettings(
158 Source settingsSource,
159 boolean isProjectSettings,
160 SettingsBuilderRequest request,
161 List<BuilderProblem> problems) {
162 if (settingsSource == null) {
163 return Settings.newInstance();
164 }
165
166 Settings settings;
167
168 try {
169 try (InputStream is = settingsSource.openStream()) {
170 settings = settingsXmlFactory.read(XmlReaderRequest.builder()
171 .inputStream(is)
172 .location(settingsSource.getLocation())
173 .strict(true)
174 .build());
175 } catch (XmlReaderException e) {
176 try (InputStream is = settingsSource.openStream()) {
177 settings = settingsXmlFactory.read(XmlReaderRequest.builder()
178 .inputStream(is)
179 .location(settingsSource.getLocation())
180 .strict(false)
181 .build());
182 Location loc = e.getCause() instanceof XMLStreamException xe ? xe.getLocation() : null;
183 problems.add(new DefaultBuilderProblem(
184 settingsSource.getLocation(),
185 loc != null ? loc.getLineNumber() : -1,
186 loc != null ? loc.getColumnNumber() : -1,
187 e,
188 e.getMessage(),
189 BuilderProblem.Severity.WARNING));
190 }
191 }
192 } catch (XmlReaderException e) {
193 Location loc = e.getCause() instanceof XMLStreamException xe ? xe.getLocation() : null;
194 problems.add(new DefaultBuilderProblem(
195 settingsSource.getLocation(),
196 loc != null ? loc.getLineNumber() : -1,
197 loc != null ? loc.getColumnNumber() : -1,
198 e,
199 "Non-parseable settings " + settingsSource.getLocation() + ": " + e.getMessage(),
200 BuilderProblem.Severity.FATAL));
201 return Settings.newInstance();
202 } catch (IOException e) {
203 problems.add(new DefaultBuilderProblem(
204 settingsSource.getLocation(),
205 -1,
206 -1,
207 e,
208 "Non-readable settings " + settingsSource.getLocation() + ": " + e.getMessage(),
209 BuilderProblem.Severity.FATAL));
210 return Settings.newInstance();
211 }
212
213 settings = interpolate(settings, request, problems);
214 settings = decrypt(settingsSource, settings, request, problems);
215
216 settingsValidator.validate(settings, isProjectSettings, problems);
217
218 if (isProjectSettings) {
219 settings = Settings.newBuilder(settings, true)
220 .localRepository(null)
221 .interactiveMode(false)
222 .offline(false)
223 .proxies(List.of())
224 .usePluginRegistry(false)
225 .servers(settings.getServers().stream()
226 .map(s -> Server.newBuilder(s, true)
227 .username(null)
228 .passphrase(null)
229 .privateKey(null)
230 .password(null)
231 .filePermissions(null)
232 .directoryPermissions(null)
233 .build())
234 .toList())
235 .build();
236 }
237
238 return settings;
239 }
240
241 private Settings interpolate(Settings settings, SettingsBuilderRequest request, List<BuilderProblem> problems) {
242 Function<String, String> src;
243 if (request.getInterpolationSource().isPresent()) {
244 src = request.getInterpolationSource().get();
245 } else {
246 Map<String, String> userProperties = request.getSession().getUserProperties();
247 Map<String, String> systemProperties = request.getSession().getSystemProperties();
248 src = Interpolator.chain(userProperties::get, systemProperties::get);
249 }
250 return new DefSettingsTransformer(value -> value != null ? interpolator.interpolate(value, src) : null)
251 .visit(settings);
252 }
253
254 static class DefSettingsTransformer extends SettingsTransformer {
255 DefSettingsTransformer(Function<String, String> transformer) {
256 super(transformer);
257 }
258
259 @Override
260 protected Activation.Builder transformActivation_Condition(
261 Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
262
263 return builder;
264 }
265 }
266
267 private Settings decrypt(
268 Source settingsSource, Settings settings, SettingsBuilderRequest request, List<BuilderProblem> problems) {
269 if (secDispatcher == null) {
270 return settings;
271 }
272 Function<String, String> decryptFunction = str -> {
273 if (secDispatcher.isAnyEncryptedString(str)) {
274 if (secDispatcher.isLegacyEncryptedString(str)) {
275
276 problems.add(new DefaultBuilderProblem(
277 settingsSource.getLocation(),
278 -1,
279 -1,
280 null,
281 "Pre-Maven 4 legacy encrypted password detected "
282 + " - configure password encryption with the help of mvnenc to be compatible with Maven 4.",
283 BuilderProblem.Severity.WARNING));
284 }
285 try {
286 return secDispatcher.decrypt(str);
287 } catch (Exception e) {
288 problems.add(new DefaultBuilderProblem(
289 settingsSource.getLocation(),
290 -1,
291 -1,
292 e,
293 "Could not decrypt password (fix the corrupted password or remove it, if unused) " + str,
294 BuilderProblem.Severity.ERROR));
295 }
296 }
297 return str;
298 };
299 return new SettingsTransformer(decryptFunction).visit(settings);
300 }
301
302 @Override
303 public List<BuilderProblem> validate(Settings settings, boolean isProjectSettings) {
304 ArrayList<BuilderProblem> problems = new ArrayList<>();
305 settingsValidator.validate(settings, isProjectSettings, problems);
306 return problems;
307 }
308
309 @Override
310 public Profile convert(org.apache.maven.api.model.Profile profile) {
311 return SettingsUtilsV4.convertToSettingsProfile(profile);
312 }
313
314 @Override
315 public org.apache.maven.api.model.Profile convert(Profile profile) {
316 return SettingsUtilsV4.convertFromSettingsProfile(profile);
317 }
318
319
320
321
322
323 static class DefaultSettingsBuilderResult implements SettingsBuilderResult {
324
325 private final Settings effectiveSettings;
326
327 private final List<BuilderProblem> problems;
328
329 DefaultSettingsBuilderResult(Settings effectiveSettings, List<BuilderProblem> problems) {
330 this.effectiveSettings = effectiveSettings;
331 this.problems = (problems != null) ? problems : new ArrayList<>();
332 }
333
334 @Override
335 public Settings getEffectiveSettings() {
336 return effectiveSettings;
337 }
338
339 @Override
340 public List<BuilderProblem> getProblems() {
341 return problems;
342 }
343 }
344 }