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