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