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