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    /** {@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 File getBasedir()
248    {
249        return this.basedir;
250    }
251
252    public void setWorkingDirectory( File workingDirectory )
253    {
254        this.workingDirectory = workingDirectory;
255    }
256
257    public ScmManager getScmManager()
258    {
259        return manager;
260    }
261
262    public ScmFileSet getFileSet()
263        throws IOException
264    {
265        if ( includes != null || excludes != null )
266        {
267            return new ScmFileSet( getWorkingDirectory(), includes, excludes );
268        }
269        else
270        {
271            return new ScmFileSet( getWorkingDirectory() );
272        }
273    }
274
275    public ScmRepository getScmRepository()
276        throws ScmException
277    {
278        ScmRepository repository;
279
280        try
281        {
282            repository = getScmManager().makeScmRepository( getConnectionUrl() );
283
284            ScmProviderRepository providerRepo = repository.getProviderRepository();
285
286            providerRepo.setPushChanges( pushChanges );
287
288            if ( !StringUtils.isEmpty( username ) )
289            {
290                providerRepo.setUser( username );
291            }
292
293            if ( !StringUtils.isEmpty( password ) )
294            {
295                providerRepo.setPassword( password );
296            }
297
298            if ( repository.getProviderRepository() instanceof ScmProviderRepositoryWithHost )
299            {
300                ScmProviderRepositoryWithHost repo = (ScmProviderRepositoryWithHost) repository.getProviderRepository();
301
302                loadInfosFromSettings( repo );
303
304                if ( !StringUtils.isEmpty( username ) )
305                {
306                    repo.setUser( username );
307                }
308
309                if ( !StringUtils.isEmpty( password ) )
310                {
311                    repo.setPassword( password );
312                }
313
314                if ( !StringUtils.isEmpty( privateKey ) )
315                {
316                    repo.setPrivateKey( privateKey );
317                }
318
319                if ( !StringUtils.isEmpty( passphrase ) )
320                {
321                    repo.setPassphrase( passphrase );
322                }
323            }
324
325            if ( !StringUtils.isEmpty( tagBase ) && repository.getProvider().equals( "svn" ) )
326            {
327                SvnScmProviderRepository svnRepo = (SvnScmProviderRepository) repository.getProviderRepository();
328
329                svnRepo.setTagBase( tagBase );
330            }
331        }
332        catch ( ScmRepositoryException e )
333        {
334            if ( !e.getValidationMessages().isEmpty() )
335            {
336                for ( String message : e.getValidationMessages() )
337                {
338                    getLog().error( message );
339                }
340            }
341
342            throw new ScmException( "Can't load the scm provider.", e );
343        }
344        catch ( Exception e )
345        {
346            throw new ScmException( "Can't load the scm provider.", e );
347        }
348
349        return repository;
350    }
351
352    /**
353     * Load username password from settings if user has not set them in JVM properties
354     *
355     * @param repo not null
356     */
357    private void loadInfosFromSettings( ScmProviderRepositoryWithHost repo )
358    {
359        if ( username == null || password == null )
360        {
361            String host = repo.getHost();
362
363            int port = repo.getPort();
364
365            if ( port > 0 )
366            {
367                host += ":" + port;
368            }
369
370            Server server = this.settings.getServer( host );
371
372            if ( server != null )
373            {
374                if ( username == null )
375                {
376                    username = server.getUsername();
377                }
378
379                if ( password == null )
380                {
381                    password = decrypt( server.getPassword(), host );
382                }
383
384                if ( privateKey == null )
385                {
386                    privateKey = server.getPrivateKey();
387                }
388
389                if ( passphrase == null )
390                {
391                    passphrase = decrypt( server.getPassphrase(), host );
392                }
393            }
394        }
395    }
396
397    private String decrypt( String str, String server )
398    {
399        try
400        {
401            return secDispatcher.decrypt( str );
402        }
403        catch ( SecDispatcherException e )
404        {
405            getLog().warn( "Failed to decrypt password/passphrase for server " + server + ", using auth token as is" );
406            return str;
407        }
408    }
409
410    public void checkResult( ScmResult result )
411        throws MojoExecutionException
412    {
413        if ( !result.isSuccess() )
414        {
415            getLog().error( "Provider message:" );
416
417            getLog().error( result.getProviderMessage() == null ? "" : result.getProviderMessage() );
418
419            getLog().error( "Command output:" );
420
421            getLog().error( result.getCommandOutput() == null ? "" : result.getCommandOutput() );
422
423            throw new MojoExecutionException(
424                "Command failed." + StringUtils.defaultString( result.getProviderMessage() ) );
425        }
426    }
427
428    public String getIncludes()
429    {
430        return includes;
431    }
432
433    public void setIncludes( String includes )
434    {
435        this.includes = includes;
436    }
437
438    public String getExcludes()
439    {
440        return excludes;
441    }
442
443    public void setExcludes( String excludes )
444    {
445        this.excludes = excludes;
446    }
447
448    public ScmVersion getScmVersion( String versionType, String version )
449        throws MojoExecutionException
450    {
451        if ( StringUtils.isEmpty( versionType ) && StringUtils.isNotEmpty( version ) )
452        {
453            throw new MojoExecutionException( "You must specify the version type." );
454        }
455
456        if ( StringUtils.isEmpty( version ) )
457        {
458            return null;
459        }
460
461        if ( "branch".equals( versionType ) )
462        {
463            return new ScmBranch( version );
464        }
465
466        if ( "tag".equals( versionType ) )
467        {
468            return new ScmTag( version );
469        }
470
471        if ( "revision".equals( versionType ) )
472        {
473            return new ScmRevision( version );
474        }
475
476        throw new MojoExecutionException( "Unknown '" + versionType + "' version type." );
477    }
478
479    protected void handleExcludesIncludesAfterCheckoutAndExport( File checkoutDirectory )
480        throws MojoExecutionException
481    {
482        List<String> includes = new ArrayList<String>();
483
484        if ( ! StringUtils.isBlank( this.getIncludes() ) )
485        {
486            String[] tokens = StringUtils.split( this.getIncludes(), "," );
487            for ( int i = 0; i < tokens.length; ++i )
488            {
489                includes.add( tokens[i] );
490            }
491        }
492
493        List<String> excludes = new ArrayList<String>();
494
495        if ( ! StringUtils.isBlank( this.getExcludes() ) )
496        {
497            String[] tokens = StringUtils.split( this.getExcludes(), "," );
498            for ( int i = 0; i < tokens.length; ++i )
499            {
500                excludes.add( tokens[i] );
501            }
502        }
503
504        if ( includes.isEmpty() && excludes.isEmpty() )
505        {
506            return;
507        }
508
509        FileSetManager fileSetManager = new FileSetManager();
510
511        FileSet fileset = new FileSet();
512        fileset.setDirectory( checkoutDirectory.getAbsolutePath() );
513        fileset.setIncludes( excludes ); // revert the order to do the delete
514        fileset.setExcludes( includes );
515        fileset.setUseDefaultExcludes( false );
516
517        try
518        {
519            fileSetManager.delete( fileset );
520        }
521        catch ( IOException e )
522        {
523            throw new MojoExecutionException( "Error found while cleaning up output directory base on "
524                + "excludes/includes configurations.", e );
525        }
526
527    }
528}