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.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.InterpolationPostProcessor;
46  import org.codehaus.plexus.interpolation.PropertiesBasedValueSource;
47  import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
48  
49  /**
50   * Builds the effective settings from a user settings file and/or a global settings file.
51   *
52   * @author Benjamin Bentmann
53   */
54  @Named
55  @Singleton
56  public class DefaultSettingsBuilder
57      implements SettingsBuilder
58  {
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( SettingsReader settingsReader,
70                                     SettingsWriter settingsWriter,
71                                     SettingsValidator settingsValidator )
72      {
73          this.settingsReader = settingsReader;
74          this.settingsWriter = settingsWriter;
75          this.settingsValidator = settingsValidator;
76      }
77  
78      public DefaultSettingsBuilder setSettingsReader( SettingsReader settingsReader )
79      {
80          this.settingsReader = settingsReader;
81          return this;
82      }
83  
84      public DefaultSettingsBuilder setSettingsWriter( SettingsWriter settingsWriter )
85      {
86          this.settingsWriter = settingsWriter;
87          return this;
88      }
89  
90      public DefaultSettingsBuilder setSettingsValidator( SettingsValidator settingsValidator )
91      {
92          this.settingsValidator = settingsValidator;
93          return this;
94      }
95  
96      @Override
97      public SettingsBuildingResult build( SettingsBuildingRequest request )
98          throws SettingsBuildingException
99      {
100         DefaultSettingsProblemCollector problems = new DefaultSettingsProblemCollector( null );
101 
102         Source globalSettingsSource =
103             getSettingsSource( request.getGlobalSettingsFile(), request.getGlobalSettingsSource() );
104         Settings globalSettings = readSettings( globalSettingsSource, request, problems );
105 
106         Source userSettingsSource =
107             getSettingsSource( request.getUserSettingsFile(), request.getUserSettingsSource() );
108         Settings userSettings = readSettings( userSettingsSource, request, problems );
109 
110         settingsMerger.merge( userSettings, globalSettings, TrackableBase.GLOBAL_LEVEL );
111 
112         problems.setSource( "" );
113 
114         userSettings = interpolate( userSettings, request, problems );
115 
116         // for the special case of a drive-relative Windows path, make sure it's absolute to save plugins from trouble
117         String localRepository = userSettings.getLocalRepository();
118         if ( localRepository != null && localRepository.length() > 0 )
119         {
120             File file = new File( localRepository );
121             if ( !file.isAbsolute() && file.getPath().startsWith( File.separator ) )
122             {
123                 userSettings.setLocalRepository( file.getAbsolutePath() );
124             }
125         }
126 
127         if ( hasErrors( problems.getProblems() ) )
128         {
129             throw new SettingsBuildingException( problems.getProblems() );
130         }
131 
132         return new DefaultSettingsBuildingResult( userSettings, 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 new Settings();
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 new Settings();
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 new Settings();
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( new InterpolationPostProcessor()
245         {
246             @Override
247             public Object execute( String expression, Object value )
248             {
249                 if ( value != null )
250                 {
251                     // we're going to parse this back in as XML so we need to escape XML markup
252                     value = value.toString().replace( "&", "&amp;" ).replace( "<", "&lt;" ).replace( ">", "&gt;" );
253                     return value;
254                 }
255                 return null;
256             }
257         } );
258 
259         try
260         {
261             serializedSettings = interpolator.interpolate( serializedSettings, "settings" );
262         }
263         catch ( InterpolationException e )
264         {
265             problems.add( SettingsProblem.Severity.ERROR, "Failed to interpolate settings: " + e.getMessage(), -1, -1,
266                           e );
267 
268             return settings;
269         }
270 
271         Settings result;
272         try
273         {
274             Map<String, ?> options = Collections.singletonMap( SettingsReader.IS_STRICT, Boolean.FALSE );
275             result = settingsReader.read( new StringReader( serializedSettings ), options );
276         }
277         catch ( IOException e )
278         {
279             problems.add( SettingsProblem.Severity.ERROR, "Failed to interpolate settings: " + e.getMessage(), -1, -1,
280                           e );
281             return settings;
282         }
283 
284         return result;
285     }
286 
287 }