View Javadoc
1   package org.apache.maven.settings.building;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.StringReader;
25  import java.io.StringWriter;
26  import java.util.Collections;
27  import java.util.List;
28  import java.util.Map;
29  
30  import javax.inject.Inject;
31  import javax.inject.Named;
32  import javax.inject.Singleton;
33  
34  import org.apache.maven.building.FileSource;
35  import org.apache.maven.building.Source;
36  import org.apache.maven.api.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.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
56      implements SettingsBuilder
57  {
58  
59      private SettingsReader settingsReader;
60  
61      private SettingsWriter settingsWriter;
62  
63      private SettingsValidator settingsValidator;
64  
65      private final MavenSettingsMerger settingsMerger = new MavenSettingsMerger();
66  
67      @Inject
68      public DefaultSettingsBuilder( SettingsReader settingsReader,
69                                     SettingsWriter settingsWriter,
70                                     SettingsValidator settingsValidator )
71      {
72          this.settingsReader = settingsReader;
73          this.settingsWriter = settingsWriter;
74          this.settingsValidator = settingsValidator;
75      }
76  
77      public DefaultSettingsBuilder setSettingsReader( SettingsReader settingsReader )
78      {
79          this.settingsReader = settingsReader;
80          return this;
81      }
82  
83      public DefaultSettingsBuilder setSettingsWriter( SettingsWriter settingsWriter )
84      {
85          this.settingsWriter = settingsWriter;
86          return this;
87      }
88  
89      public DefaultSettingsBuilder setSettingsValidator( SettingsValidator settingsValidator )
90      {
91          this.settingsValidator = settingsValidator;
92          return this;
93      }
94  
95      @Override
96      public SettingsBuildingResult build( SettingsBuildingRequest request )
97          throws SettingsBuildingException
98      {
99          DefaultSettingsProblemCollector problems = new DefaultSettingsProblemCollector( null );
100 
101         Source globalSettingsSource =
102             getSettingsSource( request.getGlobalSettingsFile(), request.getGlobalSettingsSource() );
103         Settings globalSettings = readSettings( globalSettingsSource, request, problems );
104 
105         Source userSettingsSource =
106             getSettingsSource( request.getUserSettingsFile(), request.getUserSettingsSource() );
107         Settings userSettings = readSettings( userSettingsSource, request, problems );
108 
109         userSettings = settingsMerger.merge( userSettings, globalSettings, TrackableBase.GLOBAL_LEVEL );
110 
111         problems.setSource( "" );
112 
113         userSettings = interpolate( userSettings, request, problems );
114 
115         // for the special case of a drive-relative Windows path, make sure it's absolute to save plugins from trouble
116         String localRepository = userSettings.getLocalRepository();
117         if ( localRepository != null && localRepository.length() > 0 )
118         {
119             File file = new File( localRepository );
120             if ( !file.isAbsolute() && file.getPath().startsWith( File.separator ) )
121             {
122                 userSettings = userSettings.withLocalRepository( file.getAbsolutePath() );
123             }
124         }
125 
126         if ( hasErrors( problems.getProblems() ) )
127         {
128             throw new SettingsBuildingException( problems.getProblems() );
129         }
130 
131         return new DefaultSettingsBuildingResult( new org.apache.maven.settings.Settings( userSettings ),
132                                                   problems.getProblems() );
133     }
134 
135     private boolean hasErrors( List<SettingsProblem> problems )
136     {
137         if ( problems != null )
138         {
139             for ( SettingsProblem problem : problems )
140             {
141                 if ( SettingsProblem.Severity.ERROR.compareTo( problem.getSeverity() ) >= 0 )
142                 {
143                     return true;
144                 }
145             }
146         }
147 
148         return false;
149     }
150 
151     private Source getSettingsSource( File settingsFile, Source settingsSource )
152     {
153         if ( settingsSource != null )
154         {
155             return settingsSource;
156         }
157         else if ( settingsFile != null && settingsFile.exists() )
158         {
159             return new FileSource( settingsFile );
160         }
161         return null;
162     }
163 
164     private Settings readSettings( Source settingsSource, SettingsBuildingRequest request,
165                                    DefaultSettingsProblemCollector problems )
166     {
167         if ( settingsSource == null )
168         {
169             return Settings.newInstance();
170         }
171 
172         problems.setSource( settingsSource.getLocation() );
173 
174         Settings settings;
175 
176         try
177         {
178             Map<String, ?> options = Collections.singletonMap( SettingsReader.IS_STRICT, Boolean.TRUE );
179 
180             try
181             {
182                 settings = settingsReader.read( settingsSource.getInputStream(), options );
183             }
184             catch ( SettingsParseException e )
185             {
186                 options = Collections.singletonMap( SettingsReader.IS_STRICT, Boolean.FALSE );
187 
188                 settings = settingsReader.read( settingsSource.getInputStream(), options );
189 
190                 problems.add( SettingsProblem.Severity.WARNING, e.getMessage(), e.getLineNumber(), e.getColumnNumber(),
191                               e );
192             }
193         }
194         catch ( SettingsParseException e )
195         {
196             problems.add( SettingsProblem.Severity.FATAL, "Non-parseable settings " + settingsSource.getLocation()
197                 + ": " + e.getMessage(), e.getLineNumber(), e.getColumnNumber(), e );
198             return Settings.newInstance();
199         }
200         catch ( IOException e )
201         {
202             problems.add( SettingsProblem.Severity.FATAL, "Non-readable settings " + settingsSource.getLocation()
203                 + ": " + e.getMessage(), -1, -1, e );
204             return Settings.newInstance();
205         }
206 
207         settingsValidator.validate( settings, problems );
208 
209         return settings;
210     }
211 
212     private Settings interpolate( Settings settings, SettingsBuildingRequest request,
213                                   SettingsProblemCollector problems )
214     {
215         StringWriter writer = new StringWriter( 1024 * 4 );
216 
217         try
218         {
219             settingsWriter.write( writer, null, settings );
220         }
221         catch ( IOException e )
222         {
223             throw new IllegalStateException( "Failed to serialize settings to memory", e );
224         }
225 
226         String serializedSettings = writer.toString();
227 
228         RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
229 
230         interpolator.addValueSource( new PropertiesBasedValueSource( request.getUserProperties() ) );
231 
232         interpolator.addValueSource( new PropertiesBasedValueSource( request.getSystemProperties() ) );
233 
234         try
235         {
236             interpolator.addValueSource( new EnvarBasedValueSource() );
237         }
238         catch ( IOException e )
239         {
240             problems.add( SettingsProblem.Severity.WARNING, "Failed to use environment variables for interpolation: "
241                 + e.getMessage(), -1, -1, e );
242         }
243 
244         interpolator.addPostProcessor( ( expression, value ) ->
245         {
246             if ( value != null )
247             {
248                 // we're going to parse this back in as XML so we need to escape XML markup
249                 value = value.toString().replace( "&", "&amp;" ).replace( "<", "&lt;" ).replace( ">", "&gt;" );
250                 return value;
251             }
252             return null;
253         } );
254 
255         try
256         {
257             serializedSettings = interpolator.interpolate( serializedSettings, "settings" );
258         }
259         catch ( InterpolationException e )
260         {
261             problems.add( SettingsProblem.Severity.ERROR, "Failed to interpolate settings: " + e.getMessage(), -1, -1,
262                           e );
263 
264             return settings;
265         }
266 
267         Settings result;
268         try
269         {
270             Map<String, ?> options = Collections.singletonMap( SettingsReader.IS_STRICT, Boolean.FALSE );
271             result = settingsReader.read( new StringReader( serializedSettings ), options );
272         }
273         catch ( IOException e )
274         {
275             problems.add( SettingsProblem.Severity.ERROR, "Failed to interpolate settings: " + e.getMessage(), -1, -1,
276                           e );
277             return settings;
278         }
279 
280         return result;
281     }
282 
283 }