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 (used by svn, starteam and perforce protocol).
100     */
101    @Parameter( property = "username" )
102    private String username;
103
104    /**
105     * The user password (used by svn, starteam and perforce protocol).
106     */
107    @Parameter( property = "password" )
108    private String password;
109
110    /**
111     * The private key (used by java svn).
112     */
113    @Parameter( property = "privateKey" )
114    private String privateKey;
115
116    /**
117     * The passphrase (used by java svn).
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    private String workItem;
192
193    /** {@inheritDoc} */
194    public void execute()
195        throws MojoExecutionException
196    {
197        if ( systemProperties != null )
198        {
199            // Add all system properties configured by the user
200            Iterator<Object> iter = systemProperties.keySet().iterator();
201
202            while ( iter.hasNext() )
203            {
204                String key = (String) iter.next();
205
206                String value = systemProperties.getProperty( key );
207
208                System.setProperty( key, value );
209            }
210        }
211
212        if ( providerImplementations != null && !providerImplementations.isEmpty() )
213        {
214            for ( Entry<String, String> entry : providerImplementations.entrySet() )
215            {
216                String providerType = entry.getKey();
217                String providerImplementation = entry.getValue();
218                getLog().info(
219                               "Change the default '" + providerType + "' provider implementation to '"
220                                   + providerImplementation + "'." );
221                getScmManager().setScmProviderImplementation( providerType, providerImplementation );
222            }
223        }
224    }
225
226    protected void setConnectionType( String connectionType )
227    {
228        this.connectionType = connectionType;
229    }
230
231    public String getConnectionUrl()
232    {
233        boolean requireDeveloperConnection = !"connection".equals( connectionType.toLowerCase() );
234        if ( StringUtils.isNotEmpty( connectionUrl ) && !requireDeveloperConnection )
235        {
236            return connectionUrl;
237        }
238        else if ( StringUtils.isNotEmpty( developerConnectionUrl ) )
239        {
240            return developerConnectionUrl;
241        }
242        if ( requireDeveloperConnection )
243        {
244            throw new NullPointerException( "You need to define a developerConnectionUrl parameter" );
245        }
246        else
247        {
248            throw new NullPointerException( "You need to define a connectionUrl parameter" );
249        }
250    }
251
252    public void setConnectionUrl( String connectionUrl )
253    {
254        this.connectionUrl = connectionUrl;
255    }
256
257    public File getWorkingDirectory()
258    {
259        if ( workingDirectory == null )
260        {
261            return basedir;
262        }
263
264        return workingDirectory;
265    }
266
267    public File getBasedir()
268    {
269        return this.basedir;
270    }
271
272    public void setWorkingDirectory( File workingDirectory )
273    {
274        this.workingDirectory = workingDirectory;
275    }
276
277    public ScmManager getScmManager()
278    {
279        return manager;
280    }
281
282    public ScmFileSet getFileSet()
283        throws IOException
284    {
285        if ( includes != null || excludes != null )
286        {
287            return new ScmFileSet( getWorkingDirectory(), includes, excludes );
288        }
289        else
290        {
291            return new ScmFileSet( getWorkingDirectory() );
292        }
293    }
294
295    public ScmRepository getScmRepository()
296        throws ScmException
297    {
298        ScmRepository repository;
299
300        try
301        {
302            repository = getScmManager().makeScmRepository( getConnectionUrl() );
303
304            ScmProviderRepository providerRepo = repository.getProviderRepository();
305
306            providerRepo.setPushChanges( pushChanges );
307
308            if ( !StringUtils.isEmpty( workItem ) )
309            {
310                providerRepo.setWorkItem( workItem );
311            }
312            
313            if ( !StringUtils.isEmpty( username ) )
314            {
315                providerRepo.setUser( username );
316            }
317
318            if ( !StringUtils.isEmpty( password ) )
319            {
320                providerRepo.setPassword( password );
321            }
322
323            if ( repository.getProviderRepository() instanceof ScmProviderRepositoryWithHost )
324            {
325                ScmProviderRepositoryWithHost repo = (ScmProviderRepositoryWithHost) repository.getProviderRepository();
326
327                loadInfosFromSettings( repo );
328
329                if ( !StringUtils.isEmpty( username ) )
330                {
331                    repo.setUser( username );
332                }
333
334                if ( !StringUtils.isEmpty( password ) )
335                {
336                    repo.setPassword( password );
337                }
338
339                if ( !StringUtils.isEmpty( privateKey ) )
340                {
341                    repo.setPrivateKey( privateKey );
342                }
343
344                if ( !StringUtils.isEmpty( passphrase ) )
345                {
346                    repo.setPassphrase( passphrase );
347                }
348            }
349
350            if ( !StringUtils.isEmpty( tagBase ) && repository.getProvider().equals( "svn" ) )
351            {
352                SvnScmProviderRepository svnRepo = (SvnScmProviderRepository) repository.getProviderRepository();
353
354                svnRepo.setTagBase( tagBase );
355            }
356        }
357        catch ( ScmRepositoryException e )
358        {
359            if ( !e.getValidationMessages().isEmpty() )
360            {
361                for ( String message : e.getValidationMessages() )
362                {
363                    getLog().error( message );
364                }
365            }
366
367            throw new ScmException( "Can't load the scm provider.", e );
368        }
369        catch ( Exception e )
370        {
371            throw new ScmException( "Can't load the scm provider.", e );
372        }
373
374        return repository;
375    }
376
377    /**
378     * Load username password from settings if user has not set them in JVM properties
379     *
380     * @param repo not null
381     */
382    private void loadInfosFromSettings( ScmProviderRepositoryWithHost repo )
383    {
384        if ( username == null || password == null )
385        {
386            String host = repo.getHost();
387
388            int port = repo.getPort();
389
390            if ( port > 0 )
391            {
392                host += ":" + port;
393            }
394
395            Server server = this.settings.getServer( host );
396
397            if ( server != null )
398            {
399                if ( username == null )
400                {
401                    username = server.getUsername();
402                }
403
404                if ( password == null )
405                {
406                    password = decrypt( server.getPassword(), host );
407                }
408
409                if ( privateKey == null )
410                {
411                    privateKey = server.getPrivateKey();
412                }
413
414                if ( passphrase == null )
415                {
416                    passphrase = decrypt( server.getPassphrase(), host );
417                }
418            }
419        }
420    }
421
422    private String decrypt( String str, String server )
423    {
424        try
425        {
426            return secDispatcher.decrypt( str );
427        }
428        catch ( SecDispatcherException e )
429        {
430            getLog().warn( "Failed to decrypt password/passphrase for server " + server + ", using auth token as is" );
431            return str;
432        }
433    }
434
435    public void checkResult( ScmResult result )
436        throws MojoExecutionException
437    {
438        if ( !result.isSuccess() )
439        {
440            getLog().error( "Provider message:" );
441
442            getLog().error( result.getProviderMessage() == null ? "" : result.getProviderMessage() );
443
444            getLog().error( "Command output:" );
445
446            getLog().error( result.getCommandOutput() == null ? "" : result.getCommandOutput() );
447
448            throw new MojoExecutionException(
449                "Command failed: " + Objects.toString( result.getProviderMessage() ) );
450        }
451    }
452
453    public String getIncludes()
454    {
455        return includes;
456    }
457
458    public void setIncludes( String includes )
459    {
460        this.includes = includes;
461    }
462
463    public String getExcludes()
464    {
465        return excludes;
466    }
467
468    public void setExcludes( String excludes )
469    {
470        this.excludes = excludes;
471    }
472
473    public ScmVersion getScmVersion( String versionType, String version )
474        throws MojoExecutionException
475    {
476        if ( StringUtils.isEmpty( versionType ) && StringUtils.isNotEmpty( version ) )
477        {
478            throw new MojoExecutionException( "You must specify the version type." );
479        }
480
481        if ( StringUtils.isEmpty( version ) )
482        {
483            return null;
484        }
485
486        if ( VERSION_TYPE_BRANCH.equals( versionType ) )
487        {
488            return new ScmBranch( version );
489        }
490
491        if ( VERSION_TYPE_TAG.equals( versionType ) )
492        {
493            return new ScmTag( version );
494        }
495
496        if ( VERSION_TYPE_REVISION.equals( versionType ) )
497        {
498            return new ScmRevision( version );
499        }
500
501        throw new MojoExecutionException( "Unknown '" + versionType + "' version type." );
502    }
503
504    protected void handleExcludesIncludesAfterCheckoutAndExport( File checkoutDirectory )
505        throws MojoExecutionException
506    {
507        List<String> includes = new ArrayList<String>();
508
509        if ( ! StringUtils.isBlank( this.getIncludes() ) )
510        {
511            String[] tokens = StringUtils.split( this.getIncludes(), "," );
512            for ( int i = 0; i < tokens.length; ++i )
513            {
514                includes.add( tokens[i] );
515            }
516        }
517
518        List<String> excludes = new ArrayList<String>();
519
520        if ( ! StringUtils.isBlank( this.getExcludes() ) )
521        {
522            String[] tokens = StringUtils.split( this.getExcludes(), "," );
523            for ( int i = 0; i < tokens.length; ++i )
524            {
525                excludes.add( tokens[i] );
526            }
527        }
528
529        if ( includes.isEmpty() && excludes.isEmpty() )
530        {
531            return;
532        }
533
534        FileSetManager fileSetManager = new FileSetManager();
535
536        FileSet fileset = new FileSet();
537        fileset.setDirectory( checkoutDirectory.getAbsolutePath() );
538        fileset.setIncludes( excludes ); // revert the order to do the delete
539        fileset.setExcludes( includes );
540        fileset.setUseDefaultExcludes( false );
541
542        try
543        {
544            fileSetManager.delete( fileset );
545        }
546        catch ( IOException e )
547        {
548            throw new MojoExecutionException( "Error found while cleaning up output directory base on "
549                + "excludes/includes configurations.", e );
550        }
551
552    }
553}