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.settings.building;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.File;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.StringReader;
29  import java.nio.charset.StandardCharsets;
30  import java.util.ArrayList;
31  import java.util.Collections;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.stream.Collectors;
35  import java.util.stream.Stream;
36  
37  import org.apache.maven.building.FileSource;
38  import org.apache.maven.building.Source;
39  import org.apache.maven.settings.Server;
40  import org.apache.maven.settings.Settings;
41  import org.apache.maven.settings.TrackableBase;
42  import org.apache.maven.settings.io.SettingsParseException;
43  import org.apache.maven.settings.io.SettingsReader;
44  import org.apache.maven.settings.io.SettingsWriter;
45  import org.apache.maven.settings.merge.MavenSettingsMerger;
46  import org.apache.maven.settings.validation.SettingsValidator;
47  import org.codehaus.plexus.interpolation.EnvarBasedValueSource;
48  import org.codehaus.plexus.interpolation.InterpolationException;
49  import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
50  import org.codehaus.plexus.interpolation.PropertiesBasedValueSource;
51  import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
52  import org.codehaus.plexus.util.IOUtil;
53  
54  /**
55   * Builds the effective settings from a user settings file and/or a global settings file.
56   *
57   * @author Benjamin Bentmann
58   */
59  @Named
60  @Singleton
61  public class DefaultSettingsBuilder implements SettingsBuilder {
62      private static final String MAVEN_SETTINGS_STRICT = "maven.settings.strictParsing";
63  
64      private SettingsReader settingsReader;
65  
66      private SettingsWriter settingsWriter;
67  
68      private SettingsValidator settingsValidator;
69  
70      private final MavenSettingsMerger settingsMerger = new MavenSettingsMerger();
71  
72      @Inject
73      public DefaultSettingsBuilder(
74              SettingsReader settingsReader, SettingsWriter settingsWriter, SettingsValidator settingsValidator) {
75          this.settingsReader = settingsReader;
76          this.settingsWriter = settingsWriter;
77          this.settingsValidator = settingsValidator;
78      }
79  
80      public DefaultSettingsBuilder setSettingsReader(SettingsReader settingsReader) {
81          this.settingsReader = settingsReader;
82          return this;
83      }
84  
85      public DefaultSettingsBuilder setSettingsWriter(SettingsWriter settingsWriter) {
86          this.settingsWriter = settingsWriter;
87          return this;
88      }
89  
90      public DefaultSettingsBuilder setSettingsValidator(SettingsValidator settingsValidator) {
91          this.settingsValidator = settingsValidator;
92          return this;
93      }
94  
95      @Override
96      public SettingsBuildingResult build(SettingsBuildingRequest request) throws SettingsBuildingException {
97          DefaultSettingsProblemCollector problems = new DefaultSettingsProblemCollector(null);
98  
99          boolean strict = Boolean.parseBoolean(request.getUserProperties()
100                 .getProperty(
101                         MAVEN_SETTINGS_STRICT,
102                         request.getSystemProperties().getProperty(MAVEN_SETTINGS_STRICT, Boolean.FALSE.toString())));
103 
104         Source globalSettingsSource =
105                 getSettingsSource(request.getGlobalSettingsFile(), request.getGlobalSettingsSource());
106         Settings globalSettings = readSettingsFromString(
107                 globalSettingsSource,
108                 interpolateFromSourceToString(globalSettingsSource, request, problems),
109                 request,
110                 problems,
111                 strict);
112 
113         Source userSettingsSource = getSettingsSource(request.getUserSettingsFile(), request.getUserSettingsSource());
114         Settings userSettings = readSettingsFromString(
115                 userSettingsSource,
116                 interpolateFromSourceToString(userSettingsSource, request, problems),
117                 request,
118                 problems,
119                 strict);
120 
121         settingsMerger.merge(userSettings, globalSettings, TrackableBase.GLOBAL_LEVEL);
122 
123         problems.setSource("");
124 
125         // for the special case of a drive-relative Windows path, make sure it's absolute to save plugins from trouble
126         String localRepository = userSettings.getLocalRepository();
127         if (localRepository != null && !localRepository.isEmpty()) {
128             File file = new File(localRepository);
129             if (!file.isAbsolute() && file.getPath().startsWith(File.separator)) {
130                 userSettings.setLocalRepository(file.getAbsolutePath());
131             }
132         }
133 
134         if (hasErrors(problems.getProblems())) {
135             throw new SettingsBuildingException(problems.getProblems());
136         }
137 
138         return new DefaultSettingsBuildingResult(userSettings, problems.getProblems());
139     }
140 
141     private boolean hasErrors(List<SettingsProblem> problems) {
142         if (problems != null) {
143             for (SettingsProblem problem : problems) {
144                 if (SettingsProblem.Severity.ERROR.compareTo(problem.getSeverity()) >= 0) {
145                     return true;
146                 }
147             }
148         }
149 
150         return false;
151     }
152 
153     private Source getSettingsSource(File settingsFile, Source settingsSource) {
154         if (settingsSource != null) {
155             return settingsSource;
156         } else if (settingsFile != null && settingsFile.exists()) {
157             return new FileSource(settingsFile);
158         }
159         return null;
160     }
161 
162     private Settings readSettingsFromString(
163             Source settingsSource,
164             String settingsString,
165             SettingsBuildingRequest request,
166             DefaultSettingsProblemCollector problems,
167             boolean strict) {
168         if (settingsSource == null) {
169             return new Settings();
170         }
171 
172         problems.setSource(settingsSource.getLocation());
173 
174         Settings settings;
175 
176         try {
177             Map<String, ?> options = Collections.singletonMap(SettingsReader.IS_STRICT, Boolean.TRUE);
178 
179             try {
180                 settings = settingsReader.read(new StringReader(settingsString), options);
181             } catch (SettingsParseException e) {
182                 if (strict) {
183                     problems.add(
184                             SettingsProblem.Severity.FATAL, e.getMessage(), e.getLineNumber(), e.getColumnNumber(), e);
185                     return new Settings();
186                 } else {
187                     options = Collections.singletonMap(SettingsReader.IS_STRICT, Boolean.FALSE);
188                     settings = settingsReader.read(new StringReader(settingsString), options);
189                     problems.add(
190                             SettingsProblem.Severity.WARNING,
191                             e.getMessage(),
192                             e.getLineNumber(),
193                             e.getColumnNumber(),
194                             e);
195                 }
196             }
197         } catch (SettingsParseException e) {
198             problems.add(
199                     SettingsProblem.Severity.FATAL,
200                     "Non-parseable settings " + settingsSource.getLocation() + ": " + e.getMessage(),
201                     e.getLineNumber(),
202                     e.getColumnNumber(),
203                     e);
204             return new Settings();
205         } catch (IOException e) {
206             problems.add(
207                     SettingsProblem.Severity.FATAL,
208                     "Non-readable settings " + settingsSource.getLocation() + ": " + e.getMessage(),
209                     -1,
210                     -1,
211                     e);
212             return new Settings();
213         }
214 
215         settingsValidator.validate(settings, problems);
216         settings.setServers(new ArrayList<>(serversByIds(settings.getServers())));
217 
218         return settings;
219     }
220 
221     private String interpolateFromSourceToString(
222             Source settingsSource, SettingsBuildingRequest request, SettingsProblemCollector problems) {
223         String serializedSettings = null;
224         if (settingsSource == null) {
225             return "<settings></settings>";
226         }
227         try {
228             try (InputStream inputStream = settingsSource.getInputStream()) {
229                 serializedSettings = IOUtil.toString(inputStream, StandardCharsets.UTF_8.name());
230             }
231             RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
232             interpolator.addValueSource(new PropertiesBasedValueSource(request.getUserProperties()));
233             interpolator.addValueSource(new PropertiesBasedValueSource(request.getSystemProperties()));
234 
235             try {
236                 interpolator.addValueSource(new EnvarBasedValueSource());
237             } catch (IOException e) {
238                 problems.add(
239                         SettingsProblem.Severity.WARNING,
240                         "Failed to use environment variables for interpolation: " + e.getMessage(),
241                         -1,
242                         -1,
243                         e);
244             }
245 
246             interpolator.addPostProcessor(new InterpolationPostProcessor() {
247                 @Override
248                 public Object execute(String expression, Object value) {
249                     if (value != null) {
250                         // we're going to parse this back in as XML so we need to escape XML markup
251                         value = value.toString()
252                                 .replace("&", "&amp;")
253                                 .replace("<", "&lt;")
254                                 .replace(">", "&gt;");
255                         return value;
256                     }
257                     return null;
258                 }
259             });
260 
261             try {
262                 serializedSettings = interpolator.interpolate(serializedSettings, "settings");
263             } catch (InterpolationException e) {
264                 problems.add(
265                         SettingsProblem.Severity.ERROR, "Failed to interpolate settings: " + e.getMessage(), -1, -1, e);
266 
267                 return serializedSettings;
268             }
269 
270             return serializedSettings;
271         } catch (IOException e) {
272             problems.add(
273                     SettingsProblem.Severity.ERROR, "Failed to interpolate settings: " + e.getMessage(), -1, -1, e);
274             return serializedSettings;
275         }
276     }
277 
278     private List<Server> serversByIds(List<Server> servers) {
279         return servers.stream()
280                 .flatMap(server -> Stream.concat(
281                         Stream.of(server), server.getAliases().stream().map(id -> serverAlias(server, id))))
282                 .collect(Collectors.toList());
283     }
284 
285     private Server serverAlias(Server server, String id) {
286         Server alias = server.clone();
287         alias.setId(id);
288         alias.setAliases(Collections.emptyList());
289         return alias;
290     }
291 }