View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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  
32  import org.apache.maven.api.di.Named;
33  import org.apache.maven.api.services.BuilderProblem;
34  import org.apache.maven.api.services.SettingsBuilder;
35  import org.apache.maven.api.services.SettingsBuilderException;
36  import org.apache.maven.api.services.SettingsBuilderRequest;
37  import org.apache.maven.api.services.SettingsBuilderResult;
38  import org.apache.maven.api.services.Source;
39  import org.apache.maven.api.services.xml.SettingsXmlFactory;
40  import org.apache.maven.api.services.xml.XmlReaderException;
41  import org.apache.maven.api.services.xml.XmlReaderRequest;
42  import org.apache.maven.api.settings.Profile;
43  import org.apache.maven.api.settings.Repository;
44  import org.apache.maven.api.settings.RepositoryPolicy;
45  import org.apache.maven.api.settings.Server;
46  import org.apache.maven.api.settings.Settings;
47  import org.apache.maven.settings.v4.SettingsMerger;
48  import org.apache.maven.settings.v4.SettingsTransformer;
49  import org.codehaus.plexus.interpolation.EnvarBasedValueSource;
50  import org.codehaus.plexus.interpolation.InterpolationException;
51  import org.codehaus.plexus.interpolation.MapBasedValueSource;
52  import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
53  
54  /**
55   * Builds the effective settings from a user settings file and/or a global settings file.
56   *
57   */
58  @Named
59  public class DefaultSettingsBuilder implements SettingsBuilder {
60  
61      private final DefaultSettingsValidator settingsValidator = new DefaultSettingsValidator();
62  
63      private final SettingsMerger settingsMerger = new SettingsMerger();
64  
65      @Override
66      public SettingsBuilderResult build(SettingsBuilderRequest request) throws SettingsBuilderException {
67          List<BuilderProblem> problems = new ArrayList<>();
68  
69          Source installationSource = request.getInstallationSettingsSource().orElse(null);
70          Settings installation = readSettings(installationSource, false, request, problems);
71  
72          Source projectSource = request.getProjectSettingsSource().orElse(null);
73          Settings project = readSettings(projectSource, true, request, problems);
74  
75          Source userSource = request.getUserSettingsSource().orElse(null);
76          Settings user = readSettings(userSource, false, request, problems);
77  
78          Settings effective =
79                  settingsMerger.merge(user, settingsMerger.merge(project, installation, false, null), false, null);
80  
81          // If no repository is defined in the user/global settings,
82          // it means that we have "old" settings (as those are new in 4.0)
83          // so add central to the computed settings for backward compatibility.
84          if (effective.getRepositories().isEmpty()
85                  && effective.getPluginRepositories().isEmpty()) {
86              Repository central = Repository.newBuilder()
87                      .id("central")
88                      .name("Central Repository")
89                      .url("https://repo.maven.apache.org/maven2")
90                      .snapshots(RepositoryPolicy.newBuilder().enabled(false).build())
91                      .build();
92              Repository centralWithNoUpdate = central.withReleases(
93                      RepositoryPolicy.newBuilder().updatePolicy("never").build());
94              effective = Settings.newBuilder(effective)
95                      .repositories(List.of(central))
96                      .pluginRepositories(List.of(centralWithNoUpdate))
97                      .build();
98          }
99  
100         // for the special case of a drive-relative Windows path, make sure it's absolute to save plugins from trouble
101         String localRepository = effective.getLocalRepository();
102         if (localRepository != null && !localRepository.isEmpty()) {
103             Path file = Paths.get(localRepository);
104             if (!file.isAbsolute() && file.toString().startsWith(File.separator)) {
105                 effective = effective.withLocalRepository(file.toAbsolutePath().toString());
106             }
107         }
108 
109         if (hasErrors(problems)) {
110             throw new SettingsBuilderException("Error building settings", problems);
111         }
112 
113         return new DefaultSettingsBuilderResult(effective, problems);
114     }
115 
116     private boolean hasErrors(List<BuilderProblem> problems) {
117         if (problems != null) {
118             for (BuilderProblem problem : problems) {
119                 if (BuilderProblem.Severity.ERROR.compareTo(problem.getSeverity()) >= 0) {
120                     return true;
121                 }
122             }
123         }
124 
125         return false;
126     }
127 
128     private Settings readSettings(
129             Source settingsSource,
130             boolean isProjectSettings,
131             SettingsBuilderRequest request,
132             List<BuilderProblem> problems) {
133         if (settingsSource == null) {
134             return Settings.newInstance();
135         }
136 
137         Settings settings;
138 
139         try {
140             try (InputStream is = settingsSource.openStream()) {
141                 settings = request.getSession()
142                         .getService(SettingsXmlFactory.class)
143                         .read(XmlReaderRequest.builder()
144                                 .inputStream(is)
145                                 .location(settingsSource.getLocation())
146                                 .strict(true)
147                                 .build());
148             } catch (XmlReaderException e) {
149                 try (InputStream is = settingsSource.openStream()) {
150                     settings = request.getSession()
151                             .getService(SettingsXmlFactory.class)
152                             .read(XmlReaderRequest.builder()
153                                     .inputStream(is)
154                                     .location(settingsSource.getLocation())
155                                     .strict(false)
156                                     .build());
157                     Location loc = e.getCause() instanceof XMLStreamException xe ? xe.getLocation() : null;
158                     problems.add(new DefaultBuilderProblem(
159                             settingsSource.getLocation(),
160                             loc != null ? loc.getLineNumber() : -1,
161                             loc != null ? loc.getColumnNumber() : -1,
162                             e,
163                             e.getMessage(),
164                             BuilderProblem.Severity.WARNING));
165                 }
166             }
167         } catch (XmlReaderException e) {
168             Location loc = e.getCause() instanceof XMLStreamException xe ? xe.getLocation() : null;
169             problems.add(new DefaultBuilderProblem(
170                     settingsSource.getLocation(),
171                     loc != null ? loc.getLineNumber() : -1,
172                     loc != null ? loc.getColumnNumber() : -1,
173                     e,
174                     "Non-parseable settings " + settingsSource.getLocation() + ": " + e.getMessage(),
175                     BuilderProblem.Severity.FATAL));
176             return Settings.newInstance();
177         } catch (IOException e) {
178             problems.add(new DefaultBuilderProblem(
179                     settingsSource.getLocation(),
180                     -1,
181                     -1,
182                     e,
183                     "Non-readable settings " + settingsSource.getLocation() + ": " + e.getMessage(),
184                     BuilderProblem.Severity.FATAL));
185             return Settings.newInstance();
186         }
187 
188         settings = interpolate(settings, request, problems);
189 
190         settingsValidator.validate(settings, isProjectSettings, problems);
191 
192         if (isProjectSettings) {
193             settings = Settings.newBuilder(settings, true)
194                     .localRepository(null)
195                     .interactiveMode(false)
196                     .offline(false)
197                     .proxies(List.of())
198                     .usePluginRegistry(false)
199                     .servers(settings.getServers().stream()
200                             .map(s -> Server.newBuilder(s, true)
201                                     .username(null)
202                                     .passphrase(null)
203                                     .privateKey(null)
204                                     .password(null)
205                                     .filePermissions(null)
206                                     .directoryPermissions(null)
207                                     .build())
208                             .toList())
209                     .build();
210         }
211 
212         return settings;
213     }
214 
215     private Settings interpolate(Settings settings, SettingsBuilderRequest request, List<BuilderProblem> problems) {
216 
217         RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
218 
219         interpolator.addValueSource(new MapBasedValueSource(request.getSession().getUserProperties()));
220 
221         interpolator.addValueSource(new MapBasedValueSource(request.getSession().getSystemProperties()));
222 
223         try {
224             interpolator.addValueSource(new EnvarBasedValueSource());
225         } catch (IOException e) {
226             problems.add(new DefaultBuilderProblem(
227                     null,
228                     -1,
229                     -1,
230                     e,
231                     "Failed to use environment variables for interpolation: " + e.getMessage(),
232                     BuilderProblem.Severity.WARNING));
233         }
234 
235         return new SettingsTransformer(value -> {
236                     try {
237                         return value != null ? interpolator.interpolate(value) : null;
238                     } catch (InterpolationException e) {
239                         problems.add(new DefaultBuilderProblem(
240                                 null,
241                                 -1,
242                                 -1,
243                                 e,
244                                 "Failed to interpolate settings: " + e.getMessage(),
245                                 BuilderProblem.Severity.WARNING));
246                         return value;
247                     }
248                 })
249                 .visit(settings);
250     }
251 
252     @Override
253     public List<BuilderProblem> validate(Settings settings, boolean isProjectSettings) {
254         ArrayList<BuilderProblem> problems = new ArrayList<>();
255         settingsValidator.validate(settings, isProjectSettings, problems);
256         return problems;
257     }
258 
259     @Override
260     public Profile convert(org.apache.maven.api.model.Profile profile) {
261         return SettingsUtilsV4.convertToSettingsProfile(profile);
262     }
263 
264     @Override
265     public org.apache.maven.api.model.Profile convert(Profile profile) {
266         return SettingsUtilsV4.convertFromSettingsProfile(profile);
267     }
268 
269     /**
270      * Collects the output of the settings builder.
271      *
272      */
273     static class DefaultSettingsBuilderResult implements SettingsBuilderResult {
274 
275         private final Settings effectiveSettings;
276 
277         private final List<BuilderProblem> problems;
278 
279         DefaultSettingsBuilderResult(Settings effectiveSettings, List<BuilderProblem> problems) {
280             this.effectiveSettings = effectiveSettings;
281             this.problems = (problems != null) ? problems : new ArrayList<>();
282         }
283 
284         @Override
285         public Settings getEffectiveSettings() {
286             return effectiveSettings;
287         }
288 
289         @Override
290         public List<BuilderProblem> getProblems() {
291             return problems;
292         }
293     }
294 }