001package org.apache.maven.settings.validation;
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.util.HashSet;
023import java.util.List;
024import java.util.Set;
025
026import org.apache.maven.settings.Mirror;
027import org.apache.maven.settings.Profile;
028import org.apache.maven.settings.Repository;
029import org.apache.maven.settings.Proxy;
030import org.apache.maven.settings.Server;
031import org.apache.maven.settings.Settings;
032import org.apache.maven.settings.building.SettingsProblem.Severity;
033import org.apache.maven.settings.building.SettingsProblemCollector;
034import org.codehaus.plexus.component.annotations.Component;
035import org.codehaus.plexus.util.StringUtils;
036
037/**
038 * @author Milos Kleint
039 */
040@Component( role = SettingsValidator.class )
041public class DefaultSettingsValidator
042    implements SettingsValidator
043{
044
045    private static final String ID_REGEX = "[A-Za-z0-9_\\-.]+";
046
047    private static final String ILLEGAL_FS_CHARS = "\\/:\"<>|?*";
048
049    private static final String ILLEGAL_REPO_ID_CHARS = ILLEGAL_FS_CHARS;
050
051    @Override
052    public void validate( Settings settings, SettingsProblemCollector problems )
053    {
054        if ( settings.isUsePluginRegistry() )
055        {
056            addViolation( problems, Severity.WARNING, "usePluginRegistry", null, "is deprecated and has no effect." );
057        }
058
059        List<String> pluginGroups = settings.getPluginGroups();
060
061        if ( pluginGroups != null )
062        {
063            for ( int i = 0; i < pluginGroups.size(); i++ )
064            {
065                String pluginGroup = pluginGroups.get( i ).trim();
066
067                if ( StringUtils.isBlank( pluginGroup ) )
068                {
069                    addViolation( problems, Severity.ERROR, "pluginGroups.pluginGroup[" + i + "]", null,
070                                  "must not be empty" );
071                }
072                else if ( !pluginGroup.matches( ID_REGEX ) )
073                {
074                    addViolation( problems, Severity.ERROR, "pluginGroups.pluginGroup[" + i + "]", null,
075                                  "must denote a valid group id and match the pattern " + ID_REGEX );
076                }
077            }
078        }
079
080        List<Server> servers = settings.getServers();
081
082        if ( servers != null )
083        {
084            Set<String> serverIds = new HashSet<>();
085
086            for ( int i = 0; i < servers.size(); i++ )
087            {
088                Server server = servers.get( i );
089
090                validateStringNotEmpty( problems, "servers.server[" + i + "].id", server.getId(), null );
091
092                if ( !serverIds.add( server.getId() ) )
093                {
094                    addViolation( problems, Severity.WARNING, "servers.server.id", null,
095                                  "must be unique but found duplicate server with id " + server.getId() );
096                }
097            }
098        }
099
100        List<Mirror> mirrors = settings.getMirrors();
101
102        if ( mirrors != null )
103        {
104            for ( Mirror mirror : mirrors )
105            {
106                validateStringNotEmpty( problems, "mirrors.mirror.id", mirror.getId(), mirror.getUrl() );
107
108                validateBannedCharacters( problems, "mirrors.mirror.id", Severity.WARNING, mirror.getId(), null,
109                                          ILLEGAL_REPO_ID_CHARS );
110
111                if ( "local".equals( mirror.getId() ) )
112                {
113                    addViolation( problems, Severity.WARNING, "mirrors.mirror.id", null, "must not be 'local'"
114                        + ", this identifier is reserved for the local repository"
115                        + ", using it for other repositories will corrupt your repository metadata." );
116                }
117
118                validateStringNotEmpty( problems, "mirrors.mirror.url", mirror.getUrl(), mirror.getId() );
119
120                validateStringNotEmpty( problems, "mirrors.mirror.mirrorOf", mirror.getMirrorOf(), mirror.getId() );
121            }
122        }
123
124        List<Profile> profiles = settings.getProfiles();
125
126        if ( profiles != null )
127        {
128            Set<String> profileIds = new HashSet<>();
129
130            for ( Profile profile : profiles )
131            {
132                if ( !profileIds.add( profile.getId() ) )
133                {
134                    addViolation( problems, Severity.WARNING, "profiles.profile.id", null,
135                                  "must be unique but found duplicate profile with id " + profile.getId() );
136                }
137
138                String prefix = "profiles.profile[" + profile.getId() + "].";
139
140                validateRepositories( problems, profile.getRepositories(), prefix + "repositories.repository" );
141                validateRepositories( problems, profile.getPluginRepositories(), prefix
142                    + "pluginRepositories.pluginRepository" );
143            }
144        }
145
146        List<Proxy> proxies = settings.getProxies();
147
148        if ( proxies != null )
149        {
150            Set<String> proxyIds = new HashSet<>();
151            
152            for ( Proxy proxy : proxies )
153            {
154                if ( !proxyIds.add( proxy.getId() ) )
155                {
156                    addViolation( problems, Severity.WARNING, "proxies.proxy.id", null,
157                                  "must be unique but found duplicate proxy with id " + proxy.getId() );
158                }
159                validateStringNotEmpty( problems, "proxies.proxy.host", proxy.getHost(), proxy.getId() );
160            }
161        }
162    }
163
164    private void validateRepositories( SettingsProblemCollector problems, List<Repository> repositories, String prefix )
165    {
166        Set<String> repoIds = new HashSet<>();
167
168        for ( Repository repository : repositories )
169        {
170            validateStringNotEmpty( problems, prefix + ".id", repository.getId(), repository.getUrl() );
171
172            validateBannedCharacters( problems, prefix + ".id", Severity.WARNING, repository.getId(), null,
173                                      ILLEGAL_REPO_ID_CHARS );
174
175            if ( "local".equals( repository.getId() ) )
176            {
177                addViolation( problems, Severity.WARNING, prefix + ".id", null, "must not be 'local'"
178                    + ", this identifier is reserved for the local repository"
179                    + ", using it for other repositories will corrupt your repository metadata." );
180            }
181
182            if ( !repoIds.add( repository.getId() ) )
183            {
184                addViolation( problems, Severity.WARNING, prefix + ".id", null,
185                              "must be unique but found duplicate repository with id " + repository.getId() );
186            }
187
188            validateStringNotEmpty( problems, prefix + ".url", repository.getUrl(), repository.getId() );
189
190            if ( "legacy".equals( repository.getLayout() ) )
191            {
192                addViolation( problems, Severity.WARNING, prefix + ".layout", repository.getId(),
193                              "uses the unsupported value 'legacy', artifact resolution might fail." );
194            }
195        }
196    }
197
198    // ----------------------------------------------------------------------
199    // Field validation
200    // ----------------------------------------------------------------------
201
202    /**
203     * Asserts:
204     * <p/>
205     * <ul>
206     * <li><code>string.length != null</code>
207     * <li><code>string.length > 0</code>
208     * </ul>
209     */
210    private static boolean validateStringNotEmpty( SettingsProblemCollector problems, String fieldName, String string,
211                                            String sourceHint )
212    {
213        if ( !validateNotNull( problems, fieldName, string, sourceHint ) )
214        {
215            return false;
216        }
217
218        if ( string.length() > 0 )
219        {
220            return true;
221        }
222
223        addViolation( problems, Severity.ERROR, fieldName, sourceHint, "is missing" );
224
225        return false;
226    }
227
228    /**
229     * Asserts:
230     * <p/>
231     * <ul>
232     * <li><code>string != null</code>
233     * </ul>
234     */
235    private static boolean validateNotNull( SettingsProblemCollector problems, String fieldName, Object object,
236                                            String sourceHint )
237    {
238        if ( object != null )
239        {
240            return true;
241        }
242
243        addViolation( problems, Severity.ERROR, fieldName, sourceHint, "is missing" );
244
245        return false;
246    }
247
248    private static boolean validateBannedCharacters( SettingsProblemCollector problems, String fieldName,
249                                                     Severity severity, String string, String sourceHint,
250                                                     String banned )
251    {
252        if ( string != null )
253        {
254            for ( int i = string.length() - 1; i >= 0; i-- )
255            {
256                if ( banned.indexOf( string.charAt( i ) ) >= 0 )
257                {
258                    addViolation( problems, severity, fieldName, sourceHint,
259                                  "must not contain any of these characters " + banned + " but found "
260                                      + string.charAt( i ) );
261                    return false;
262                }
263            }
264        }
265
266        return true;
267    }
268
269    private static void addViolation( SettingsProblemCollector problems, Severity severity, String fieldName,
270                               String sourceHint, String message )
271    {
272        StringBuilder buffer = new StringBuilder( 256 );
273        buffer.append( '\'' ).append( fieldName ).append( '\'' );
274
275        if ( sourceHint != null )
276        {
277            buffer.append( " for " ).append( sourceHint );
278        }
279
280        buffer.append( ' ' ).append( message );
281
282        problems.add( severity, buffer.toString(), -1, -1, null );
283    }
284
285}