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( "&", "&amp;" ).replace( "<", "&lt;" ).replace( ">", "&gt;" );
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}