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.util.Collections;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  
32  import org.apache.maven.api.settings.InputSource;
33  import org.apache.maven.building.FileSource;
34  import org.apache.maven.building.Source;
35  import org.apache.maven.settings.Repository;
36  import org.apache.maven.settings.RepositoryPolicy;
37  import org.apache.maven.settings.Server;
38  import org.apache.maven.settings.Settings;
39  import org.apache.maven.settings.TrackableBase;
40  import org.apache.maven.settings.io.SettingsParseException;
41  import org.apache.maven.settings.io.SettingsReader;
42  import org.apache.maven.settings.io.SettingsWriter;
43  import org.apache.maven.settings.merge.MavenSettingsMerger;
44  import org.apache.maven.settings.v4.SettingsTransformer;
45  import org.apache.maven.settings.validation.SettingsValidator;
46  import org.codehaus.plexus.interpolation.EnvarBasedValueSource;
47  import org.codehaus.plexus.interpolation.InterpolationException;
48  import org.codehaus.plexus.interpolation.PropertiesBasedValueSource;
49  import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
50  
51  /**
52   * Builds the effective settings from a user settings file and/or a global settings file.
53   *
54   * @author Benjamin Bentmann
55   */
56  @Named
57  @Singleton
58  public class DefaultSettingsBuilder implements SettingsBuilder {
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          Source globalSettingsSource =
96                  getSettingsSource(request.getGlobalSettingsFile(), request.getGlobalSettingsSource());
97          Settings globalSettings = readSettings(globalSettingsSource, false, request, problems);
98  
99          Source projectSettingsSource =
100                 getSettingsSource(request.getProjectSettingsFile(), request.getProjectSettingsSource());
101         Settings projectSettings = readSettings(projectSettingsSource, true, request, problems);
102 
103         Source userSettingsSource = getSettingsSource(request.getUserSettingsFile(), request.getUserSettingsSource());
104         Settings userSettings = readSettings(userSettingsSource, false, request, problems);
105 
106         settingsMerger.merge(projectSettings, globalSettings, TrackableBase.GLOBAL_LEVEL);
107         settingsMerger.merge(userSettings, projectSettings, TrackableBase.PROJECT_LEVEL);
108 
109         // If no repository is defined in the user/global settings,
110         // it means that we have "old" settings (as those are new in 4.0)
111         // so add central to the computed settings for backward compatibility.
112         if (userSettings.getRepositories().isEmpty()
113                 && userSettings.getPluginRepositories().isEmpty()) {
114             Repository central = new Repository();
115             central.setId("central");
116             central.setName("Central Repository");
117             central.setUrl("https://repo.maven.apache.org/maven2");
118             RepositoryPolicy disabledPolicy = new RepositoryPolicy();
119             disabledPolicy.setEnabled(false);
120             central.setSnapshots(disabledPolicy);
121             userSettings.getRepositories().add(central);
122             central = central.clone();
123             RepositoryPolicy updateNeverPolicy = new RepositoryPolicy();
124             disabledPolicy.setUpdatePolicy("never");
125             central.setReleases(updateNeverPolicy);
126             userSettings.getPluginRepositories().add(central);
127         }
128 
129         problems.setSource("");
130 
131         userSettings = interpolate(userSettings, request, problems);
132 
133         // for the special case of a drive-relative Windows path, make sure it's absolute to save plugins from trouble
134         String localRepository = userSettings.getLocalRepository();
135         if (localRepository != null && localRepository.length() > 0) {
136             File file = new File(localRepository);
137             if (!file.isAbsolute() && file.getPath().startsWith(File.separator)) {
138                 userSettings.setLocalRepository(file.getAbsolutePath());
139             }
140         }
141 
142         if (hasErrors(problems.getProblems())) {
143             throw new SettingsBuildingException(problems.getProblems());
144         }
145 
146         return new DefaultSettingsBuildingResult(userSettings, problems.getProblems());
147     }
148 
149     private boolean hasErrors(List<SettingsProblem> problems) {
150         if (problems != null) {
151             for (SettingsProblem problem : problems) {
152                 if (SettingsProblem.Severity.ERROR.compareTo(problem.getSeverity()) >= 0) {
153                     return true;
154                 }
155             }
156         }
157 
158         return false;
159     }
160 
161     private Source getSettingsSource(File settingsFile, Source settingsSource) {
162         if (settingsSource != null) {
163             return settingsSource;
164         } else if (settingsFile != null && settingsFile.exists()) {
165             return new FileSource(settingsFile);
166         }
167         return null;
168     }
169 
170     private Settings readSettings(
171             Source settingsSource,
172             boolean isProjectSettings,
173             SettingsBuildingRequest request,
174             DefaultSettingsProblemCollector problems) {
175         if (settingsSource == null) {
176             return new Settings();
177         }
178 
179         problems.setSource(settingsSource.getLocation());
180 
181         Settings settings;
182 
183         try {
184             Map<String, Object> options = new HashMap<>();
185             options.put(SettingsReader.IS_STRICT, Boolean.TRUE);
186             options.put(InputSource.class.getName(), new InputSource(settingsSource.getLocation()));
187             try {
188                 settings = settingsReader.read(settingsSource.getInputStream(), options);
189             } catch (SettingsParseException e) {
190                 options = Collections.singletonMap(SettingsReader.IS_STRICT, Boolean.FALSE);
191 
192                 settings = settingsReader.read(settingsSource.getInputStream(), options);
193 
194                 problems.add(
195                         SettingsProblem.Severity.WARNING, e.getMessage(), e.getLineNumber(), e.getColumnNumber(), e);
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, isProjectSettings, problems);
216 
217         if (isProjectSettings) {
218             settings.setLocalRepository(null);
219             settings.setInteractiveMode(true);
220             settings.setOffline(false);
221             settings.setProxies(Collections.emptyList());
222             settings.setUsePluginRegistry(false);
223             for (Server server : settings.getServers()) {
224                 server.setUsername(null);
225                 server.setPassword(null);
226                 server.setPrivateKey(null);
227                 server.setPassword(null);
228                 server.setFilePermissions(null);
229                 server.setDirectoryPermissions(null);
230             }
231         }
232 
233         return settings;
234     }
235 
236     private Settings interpolate(
237             Settings settings, SettingsBuildingRequest request, SettingsProblemCollector problems) {
238 
239         RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
240 
241         interpolator.addValueSource(new PropertiesBasedValueSource(request.getUserProperties()));
242 
243         interpolator.addValueSource(new PropertiesBasedValueSource(request.getSystemProperties()));
244 
245         try {
246             interpolator.addValueSource(new EnvarBasedValueSource());
247         } catch (IOException e) {
248             problems.add(
249                     SettingsProblem.Severity.WARNING,
250                     "Failed to use environment variables for interpolation: " + e.getMessage(),
251                     -1,
252                     -1,
253                     e);
254         }
255 
256         return new Settings(new SettingsTransformer(value -> {
257                     try {
258                         return value != null ? interpolator.interpolate(value) : null;
259                     } catch (InterpolationException e) {
260                         problems.add(
261                                 SettingsProblem.Severity.WARNING,
262                                 "Failed to interpolate settings: " + e.getMessage(),
263                                 -1,
264                                 -1,
265                                 e);
266                         return value;
267                     }
268                 })
269                 .visit(settings.getDelegate()));
270     }
271 }