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