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