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