001 package org.apache.maven.settings.building;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements. See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership. The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License. You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied. See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022 import java.io.File;
023 import java.io.IOException;
024 import java.io.StringReader;
025 import java.io.StringWriter;
026 import java.util.Collections;
027 import java.util.List;
028 import java.util.Map;
029
030 import org.apache.maven.settings.Settings;
031 import org.apache.maven.settings.TrackableBase;
032 import org.apache.maven.settings.io.SettingsParseException;
033 import org.apache.maven.settings.io.SettingsReader;
034 import org.apache.maven.settings.io.SettingsWriter;
035 import org.apache.maven.settings.merge.MavenSettingsMerger;
036 import org.apache.maven.settings.validation.SettingsValidator;
037 import org.codehaus.plexus.component.annotations.Component;
038 import org.codehaus.plexus.component.annotations.Requirement;
039 import org.codehaus.plexus.interpolation.EnvarBasedValueSource;
040 import org.codehaus.plexus.interpolation.InterpolationException;
041 import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
042 import org.codehaus.plexus.interpolation.PropertiesBasedValueSource;
043 import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
044
045 /**
046 * Builds the effective settings from a user settings file and/or a global settings file.
047 *
048 * @author Benjamin Bentmann
049 */
050 @Component( role = SettingsBuilder.class )
051 public class DefaultSettingsBuilder
052 implements SettingsBuilder
053 {
054
055 @Requirement
056 private SettingsReader settingsReader;
057
058 @Requirement
059 private SettingsWriter settingsWriter;
060
061 @Requirement
062 private SettingsValidator settingsValidator;
063
064 private MavenSettingsMerger settingsMerger = new MavenSettingsMerger();
065
066 public DefaultSettingsBuilder setSettingsReader( SettingsReader settingsReader )
067 {
068 this.settingsReader = settingsReader;
069 return this;
070 }
071
072 public DefaultSettingsBuilder setSettingsWriter( SettingsWriter settingsWriter )
073 {
074 this.settingsWriter = settingsWriter;
075 return this;
076 }
077
078 public DefaultSettingsBuilder setSettingsValidator( SettingsValidator settingsValidator )
079 {
080 this.settingsValidator = settingsValidator;
081 return this;
082 }
083
084 public SettingsBuildingResult build( SettingsBuildingRequest request )
085 throws SettingsBuildingException
086 {
087 DefaultSettingsProblemCollector problems = new DefaultSettingsProblemCollector( null );
088
089 SettingsSource globalSettingsSource =
090 getSettingsSource( request.getGlobalSettingsFile(), request.getGlobalSettingsSource() );
091 Settings globalSettings = readSettings( globalSettingsSource, request, problems );
092
093 SettingsSource userSettingsSource =
094 getSettingsSource( request.getUserSettingsFile(), request.getUserSettingsSource() );
095 Settings userSettings = readSettings( userSettingsSource, request, problems );
096
097 settingsMerger.merge( userSettings, globalSettings, TrackableBase.GLOBAL_LEVEL );
098
099 problems.setSource( "" );
100
101 userSettings = interpolate( userSettings, request, problems );
102
103 // for the special case of a drive-relative Windows path, make sure it's absolute to save plugins from trouble
104 String localRepository = userSettings.getLocalRepository();
105 if ( localRepository != null && localRepository.length() > 0 )
106 {
107 File file = new File( localRepository );
108 if ( !file.isAbsolute() && file.getPath().startsWith( File.separator ) )
109 {
110 userSettings.setLocalRepository( file.getAbsolutePath() );
111 }
112 }
113
114 if ( hasErrors( problems.getProblems() ) )
115 {
116 throw new SettingsBuildingException( problems.getProblems() );
117 }
118
119 return new DefaultSettingsBuildingResult( userSettings, problems.getProblems() );
120 }
121
122 private boolean hasErrors( List<SettingsProblem> problems )
123 {
124 if ( problems != null )
125 {
126 for ( SettingsProblem problem : problems )
127 {
128 if ( SettingsProblem.Severity.ERROR.compareTo( problem.getSeverity() ) >= 0 )
129 {
130 return true;
131 }
132 }
133 }
134
135 return false;
136 }
137
138 private SettingsSource getSettingsSource( File settingsFile, SettingsSource settingsSource )
139 {
140 if ( settingsSource != null )
141 {
142 return settingsSource;
143 }
144 else if ( settingsFile != null && settingsFile.exists() )
145 {
146 return new FileSettingsSource( settingsFile );
147 }
148 return null;
149 }
150
151 private Settings readSettings( SettingsSource settingsSource, SettingsBuildingRequest request,
152 DefaultSettingsProblemCollector problems )
153 {
154 if ( settingsSource == null )
155 {
156 return new Settings();
157 }
158
159 problems.setSource( settingsSource.getLocation() );
160
161 Settings settings;
162
163 try
164 {
165 Map<String, ?> options = Collections.singletonMap( SettingsReader.IS_STRICT, Boolean.TRUE );
166
167 try
168 {
169 settings = settingsReader.read( settingsSource.getInputStream(), options );
170 }
171 catch ( SettingsParseException e )
172 {
173 options = Collections.singletonMap( SettingsReader.IS_STRICT, Boolean.FALSE );
174
175 settings = settingsReader.read( settingsSource.getInputStream(), options );
176
177 problems.add( SettingsProblem.Severity.WARNING, e.getMessage(), e.getLineNumber(), e.getColumnNumber(),
178 e );
179 }
180 }
181 catch ( SettingsParseException e )
182 {
183 problems.add( SettingsProblem.Severity.FATAL, "Non-parseable settings " + settingsSource.getLocation() + ": "
184 + e.getMessage(), e.getLineNumber(), e.getColumnNumber(), e );
185 return new Settings();
186 }
187 catch ( IOException e )
188 {
189 problems.add( SettingsProblem.Severity.FATAL, "Non-readable settings " + settingsSource.getLocation() + ": "
190 + e.getMessage(), -1, -1, e );
191 return new Settings();
192 }
193
194 settingsValidator.validate( settings, problems );
195
196 return settings;
197 }
198
199 private Settings interpolate( Settings settings, SettingsBuildingRequest request, SettingsProblemCollector problems )
200 {
201 StringWriter writer = new StringWriter( 1024 * 4 );
202
203 try
204 {
205 settingsWriter.write( writer, null, settings );
206 }
207 catch ( IOException e )
208 {
209 throw new IllegalStateException( "Failed to serialize settings to memory", e );
210 }
211
212 String serializedSettings = writer.toString();
213
214 RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
215
216 interpolator.addValueSource( new PropertiesBasedValueSource( request.getUserProperties() ) );
217
218 interpolator.addValueSource( new PropertiesBasedValueSource( request.getSystemProperties() ) );
219
220 try
221 {
222 interpolator.addValueSource( new EnvarBasedValueSource() );
223 }
224 catch ( IOException e )
225 {
226 problems.add( SettingsProblem.Severity.WARNING, "Failed to use environment variables for interpolation: "
227 + e.getMessage(), -1, -1, e );
228 }
229
230 interpolator.addPostProcessor( new InterpolationPostProcessor()
231 {
232 public Object execute( String expression, Object value )
233 {
234 if ( value != null )
235 {
236 // we're going to parse this back in as XML so we need to escape XML markup
237 value = value.toString().replace( "&", "&" ).replace( "<", "<" ).replace( ">", ">" );
238 return value;
239 }
240 return null;
241 }
242 } );
243
244 try
245 {
246 serializedSettings = interpolator.interpolate( serializedSettings, "settings" );
247 }
248 catch ( InterpolationException e )
249 {
250 problems.add( SettingsProblem.Severity.ERROR, "Failed to interpolate settings: " + e.getMessage(), -1, -1,
251 e );
252
253 return settings;
254 }
255
256 Settings result;
257 try
258 {
259 Map<String, ?> options = Collections.singletonMap( SettingsReader.IS_STRICT, Boolean.FALSE );
260 result = settingsReader.read( new StringReader( serializedSettings ), options );
261 }
262 catch ( IOException e )
263 {
264 problems.add( SettingsProblem.Severity.ERROR, "Failed to interpolate settings: " + e.getMessage(), -1, -1,
265 e );
266 return settings;
267 }
268
269 return result;
270 }
271
272 }