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