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