001package org.apache.maven.scm.plugin;
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.util.ArrayList;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028import java.util.Map.Entry;
029import java.util.Objects;
030import java.util.Properties;
031
032import org.apache.maven.plugin.AbstractMojo;
033import org.apache.maven.plugin.MojoExecutionException;
034import org.apache.maven.plugins.annotations.Component;
035import org.apache.maven.plugins.annotations.Parameter;
036import org.apache.maven.scm.ScmBranch;
037import org.apache.maven.scm.ScmException;
038import org.apache.maven.scm.ScmFileSet;
039import org.apache.maven.scm.ScmResult;
040import org.apache.maven.scm.ScmRevision;
041import org.apache.maven.scm.ScmTag;
042import org.apache.maven.scm.ScmVersion;
043import org.apache.maven.scm.manager.ScmManager;
044import org.apache.maven.scm.provider.ScmProviderRepository;
045import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost;
046import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository;
047import org.apache.maven.scm.repository.ScmRepository;
048import org.apache.maven.scm.repository.ScmRepositoryException;
049import org.apache.maven.settings.Server;
050import org.apache.maven.settings.Settings;
051import org.apache.maven.shared.model.fileset.FileSet;
052import org.apache.maven.shared.model.fileset.util.FileSetManager;
053import org.codehaus.plexus.util.StringUtils;
054import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;
055import org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException;
056
057/**
058 * @author <a href="evenisse@apache.org">Emmanuel Venisse</a>
059 * @author Olivier Lamy
060 */
061public abstract class AbstractScmMojo
062    extends AbstractMojo
063{
064
065    protected static final String VERSION_TYPE_BRANCH = "branch";
066
067    protected static final String VERSION_TYPE_REVISION = "revision";
068
069    protected static final String VERSION_TYPE_TAG = "tag";
070
071    protected static final String[] VALID_VERSION_TYPES = { VERSION_TYPE_BRANCH,
072            VERSION_TYPE_REVISION, VERSION_TYPE_TAG };
073
074    /**
075     * The SCM connection URL.
076     */
077    @Parameter( property = "connectionUrl", defaultValue = "${project.scm.connection}" )
078    private String connectionUrl;
079
080    /**
081     * The SCM connection URL for developers.
082     */
083    @Parameter( property = "developerConnectionUrl", defaultValue = "${project.scm.developerConnection}" )
084    private String developerConnectionUrl;
085
086    /**
087     * The type of connection to use (connection or developerConnection).
088     */
089    @Parameter( property = "connectionType", defaultValue = "connection" )
090    private String connectionType;
091
092    /**
093     * The working directory.
094     */
095    @Parameter( property = "workingDirectory" )
096    private File workingDirectory;
097
098    /**
099     * The user name.
100     */
101    @Parameter( property = "username" )
102    private String username;
103
104    /**
105     * The user password.
106     */
107    @Parameter( property = "password" )
108    private String password;
109
110    /**
111     * The private key.
112     */
113    @Parameter( property = "privateKey" )
114    private String privateKey;
115
116    /**
117     * The passphrase.
118     */
119    @Parameter( property = "passphrase" )
120    private String passphrase;
121
122    /**
123     * The url of tags base directory (used by svn protocol). It is not
124     * necessary to set it if you use the standard svn layout
125     * (branches/tags/trunk).
126     */
127    @Parameter( property = "tagBase" )
128    private String tagBase;
129
130    /**
131     * Comma separated list of includes file pattern.
132     */
133    @Parameter( property = "includes" )
134    private String includes;
135
136    /**
137     * Comma separated list of excludes file pattern.
138     */
139    @Parameter( property = "excludes" )
140    private String excludes;
141
142    @Component
143    private ScmManager manager;
144
145    /**
146     * When this plugin requires Maven 3.0 as minimum, this component can be removed and o.a.m.s.c.SettingsDecrypter be
147     * used instead.
148     */
149    @Component( hint = "mng-4384" )
150    private SecDispatcher secDispatcher;
151
152    /**
153     * The base directory.
154     */
155    @Parameter( property = "basedir", required = true )
156    private File basedir;
157
158    @Parameter( defaultValue = "${settings}", readonly = true )
159    private Settings settings;
160
161    /**
162     * List of System properties to pass to the JUnit tests.
163     */
164    @Parameter
165    private Properties systemProperties;
166
167    /**
168     * List of provider implementations.
169     */
170    @Parameter
171    private Map<String, String> providerImplementations;
172
173    /**
174     * Should distributed changes be pushed to the central repository?
175     * For many distributed SCMs like Git, a change like a commit
176     * is only stored in your local copy of the repository.  Pushing
177     * the change allows your to more easily share it with other users.
178     *
179     * @since 1.4
180     */
181    @Parameter( property = "pushChanges", defaultValue = "true" )
182    private boolean pushChanges;
183
184    /**
185     * A workItem for SCMs like RTC, TFS etc, that may require additional
186     * information to perform a pushChange operation.
187     *
188     * @since 1.9.5
189     */
190    @Parameter( property = "workItem" )
191    @Deprecated
192    private String workItem;
193
194    /** {@inheritDoc} */
195    public void execute()
196        throws MojoExecutionException
197    {
198        if ( systemProperties != null )
199        {
200            // Add all system properties configured by the user
201            Iterator<Object> iter = systemProperties.keySet().iterator();
202
203            while ( iter.hasNext() )
204            {
205                String key = (String) iter.next();
206
207                String value = systemProperties.getProperty( key );
208
209                System.setProperty( key, value );
210            }
211        }
212
213        if ( providerImplementations != null && !providerImplementations.isEmpty() )
214        {
215            for ( Entry<String, String> entry : providerImplementations.entrySet() )
216            {
217                String providerType = entry.getKey();
218                String providerImplementation = entry.getValue();
219                getLog().info(
220                               "Change the default '" + providerType + "' provider implementation to '"
221                                   + providerImplementation + "'." );
222                getScmManager().setScmProviderImplementation( providerType, providerImplementation );
223            }
224        }
225    }
226
227    protected void setConnectionType( String connectionType )
228    {
229        this.connectionType = connectionType;
230    }
231
232    public String getConnectionUrl()
233    {
234        boolean requireDeveloperConnection = !"connection".equals( connectionType.toLowerCase() );
235        if ( StringUtils.isNotEmpty( connectionUrl ) && !requireDeveloperConnection )
236        {
237            return connectionUrl;
238        }
239        else if ( StringUtils.isNotEmpty( developerConnectionUrl ) )
240        {
241            return developerConnectionUrl;
242        }
243        if ( requireDeveloperConnection )
244        {
245            throw new NullPointerException( "You need to define a developerConnectionUrl parameter" );
246        }
247        else
248        {
249            throw new NullPointerException( "You need to define a connectionUrl parameter" );
250        }
251    }
252
253    public void setConnectionUrl( String connectionUrl )
254    {
255        this.connectionUrl = connectionUrl;
256    }
257
258    public File getWorkingDirectory()
259    {
260        if ( workingDirectory == null )
261        {
262            return basedir;
263        }
264
265        return workingDirectory;
266    }
267
268    public File getBasedir()
269    {
270        return this.basedir;
271    }
272
273    public void setWorkingDirectory( File workingDirectory )
274    {
275        this.workingDirectory = workingDirectory;
276    }
277
278    public ScmManager getScmManager()
279    {
280        return manager;
281    }
282
283    public ScmFileSet getFileSet()
284        throws IOException
285    {
286        if ( includes != null || excludes != null )
287        {
288            return new ScmFileSet( getWorkingDirectory(), includes, excludes );
289        }
290        else
291        {
292            return new ScmFileSet( getWorkingDirectory() );
293        }
294    }
295
296    public ScmRepository getScmRepository()
297        throws ScmException
298    {
299        ScmRepository repository;
300
301        try
302        {
303            repository = getScmManager().makeScmRepository( getConnectionUrl() );
304
305            ScmProviderRepository providerRepo = repository.getProviderRepository();
306
307            providerRepo.setPushChanges( pushChanges );
308
309            if ( !StringUtils.isEmpty( workItem ) )
310            {
311                providerRepo.setWorkItem( workItem );
312            }
313
314            if ( !StringUtils.isEmpty( username ) )
315            {
316                providerRepo.setUser( username );
317            }
318
319            if ( !StringUtils.isEmpty( password ) )
320            {
321                providerRepo.setPassword( password );
322            }
323
324            if ( repository.getProviderRepository() instanceof ScmProviderRepositoryWithHost )
325            {
326                ScmProviderRepositoryWithHost repo = (ScmProviderRepositoryWithHost) repository.getProviderRepository();
327
328                loadInfosFromSettings( repo );
329
330                if ( !StringUtils.isEmpty( username ) )
331                {
332                    repo.setUser( username );
333                }
334
335                if ( !StringUtils.isEmpty( password ) )
336                {
337                    repo.setPassword( password );
338                }
339
340                if ( !StringUtils.isEmpty( privateKey ) )
341                {
342                    repo.setPrivateKey( privateKey );
343                }
344
345                if ( !StringUtils.isEmpty( passphrase ) )
346                {
347                    repo.setPassphrase( passphrase );
348                }
349            }
350
351            if ( !StringUtils.isEmpty( tagBase ) && repository.getProvider().equals( "svn" ) )
352            {
353                SvnScmProviderRepository svnRepo = (SvnScmProviderRepository) repository.getProviderRepository();
354
355                svnRepo.setTagBase( tagBase );
356            }
357        }
358        catch ( ScmRepositoryException e )
359        {
360            if ( !e.getValidationMessages().isEmpty() )
361            {
362                for ( String message : e.getValidationMessages() )
363                {
364                    getLog().error( message );
365                }
366            }
367
368            throw new ScmException( "Can't load the scm provider.", e );
369        }
370        catch ( Exception e )
371        {
372            throw new ScmException( "Can't load the scm provider.", e );
373        }
374
375        return repository;
376    }
377
378    /**
379     * Load username password from settings if user has not set them in JVM properties
380     *
381     * @param repo not null
382     */
383    private void loadInfosFromSettings( ScmProviderRepositoryWithHost repo )
384    {
385        if ( username == null || password == null )
386        {
387            String host = repo.getHost();
388
389            int port = repo.getPort();
390
391            if ( port > 0 )
392            {
393                host += ":" + port;
394            }
395
396            Server server = this.settings.getServer( host );
397
398            if ( server != null )
399            {
400                if ( username == null )
401                {
402                    username = server.getUsername();
403                }
404
405                if ( password == null )
406                {
407                    password = decrypt( server.getPassword(), host );
408                }
409
410                if ( privateKey == null )
411                {
412                    privateKey = server.getPrivateKey();
413                }
414
415                if ( passphrase == null )
416                {
417                    passphrase = decrypt( server.getPassphrase(), host );
418                }
419            }
420        }
421    }
422
423    private String decrypt( String str, String server )
424    {
425        try
426        {
427            return secDispatcher.decrypt( str );
428        }
429        catch ( SecDispatcherException e )
430        {
431            getLog().warn( "Failed to decrypt password/passphrase for server " + server + ", using auth token as is" );
432            return str;
433        }
434    }
435
436    public void checkResult( ScmResult result )
437        throws MojoExecutionException
438    {
439        if ( !result.isSuccess() )
440        {
441            getLog().error( "Provider message:" );
442
443            getLog().error( result.getProviderMessage() == null ? "" : result.getProviderMessage() );
444
445            getLog().error( "Command output:" );
446
447            getLog().error( result.getCommandOutput() == null ? "" : result.getCommandOutput() );
448
449            throw new MojoExecutionException(
450                "Command failed: " + Objects.toString( result.getProviderMessage() ) );
451        }
452    }
453
454    public String getIncludes()
455    {
456        return includes;
457    }
458
459    public void setIncludes( String includes )
460    {
461        this.includes = includes;
462    }
463
464    public String getExcludes()
465    {
466        return excludes;
467    }
468
469    public void setExcludes( String excludes )
470    {
471        this.excludes = excludes;
472    }
473
474    public ScmVersion getScmVersion( String versionType, String version )
475        throws MojoExecutionException
476    {
477        if ( StringUtils.isEmpty( versionType ) && StringUtils.isNotEmpty( version ) )
478        {
479            throw new MojoExecutionException( "You must specify the version type." );
480        }
481
482        if ( StringUtils.isEmpty( version ) )
483        {
484            return null;
485        }
486
487        if ( VERSION_TYPE_BRANCH.equals( versionType ) )
488        {
489            return new ScmBranch( version );
490        }
491
492        if ( VERSION_TYPE_TAG.equals( versionType ) )
493        {
494            return new ScmTag( version );
495        }
496
497        if ( VERSION_TYPE_REVISION.equals( versionType ) )
498        {
499            return new ScmRevision( version );
500        }
501
502        throw new MojoExecutionException( "Unknown '" + versionType + "' version type." );
503    }
504
505    protected void handleExcludesIncludesAfterCheckoutAndExport( File checkoutDirectory )
506        throws MojoExecutionException
507    {
508        List<String> includes = new ArrayList<String>();
509
510        if ( ! StringUtils.isBlank( this.getIncludes() ) )
511        {
512            String[] tokens = StringUtils.split( this.getIncludes(), "," );
513            for ( int i = 0; i < tokens.length; ++i )
514            {
515                includes.add( tokens[i] );
516            }
517        }
518
519        List<String> excludes = new ArrayList<String>();
520
521        if ( ! StringUtils.isBlank( this.getExcludes() ) )
522        {
523            String[] tokens = StringUtils.split( this.getExcludes(), "," );
524            for ( int i = 0; i < tokens.length; ++i )
525            {
526                excludes.add( tokens[i] );
527            }
528        }
529
530        if ( includes.isEmpty() && excludes.isEmpty() )
531        {
532            return;
533        }
534
535        FileSetManager fileSetManager = new FileSetManager();
536
537        FileSet fileset = new FileSet();
538        fileset.setDirectory( checkoutDirectory.getAbsolutePath() );
539        fileset.setIncludes( excludes ); // revert the order to do the delete
540        fileset.setExcludes( includes );
541        fileset.setUseDefaultExcludes( false );
542
543        try
544        {
545            fileSetManager.delete( fileset );
546        }
547        catch ( IOException e )
548        {
549            throw new MojoExecutionException( "Error found while cleaning up output directory base on "
550                + "excludes/includes configurations.", e );
551        }
552
553    }
554}