Coverage Report - org.apache.maven.shared.release.exec.InvokerMavenExecutor
 
Classes in this File Line Coverage Branch Coverage Complexity
InvokerMavenExecutor
18%
24/133
0%
0/78
2,958
InvokerMavenExecutor$Handler
0%
0/5
N/A
2,958
InvokerMavenExecutor$LoggerBridge
0%
0/32
N/A
2,958
 
 1  
 package org.apache.maven.shared.release.exec;
 2  
 
 3  
 /*
 4  
  * Licensed to the Apache Software Foundation (ASF) under one
 5  
  * or more contributor license agreements.  See the NOTICE file
 6  
  * distributed with this work for additional information
 7  
  * regarding copyright ownership.  The ASF licenses this file
 8  
  * to you under the Apache License, Version 2.0 (the
 9  
  * "License"); you may not use this file except in compliance
 10  
  * with the License.  You may obtain a copy of the License at
 11  
  *
 12  
  *   http://www.apache.org/licenses/LICENSE-2.0
 13  
  *
 14  
  * Unless required by applicable law or agreed to in writing,
 15  
  * software distributed under the License is distributed on an
 16  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 17  
  * KIND, either express or implied.  See the License for the
 18  
  * specific language governing permissions and limitations
 19  
  * under the License.
 20  
  */
 21  
 
 22  
 import org.apache.commons.cli.CommandLine;
 23  
 import org.apache.commons.cli.OptionBuilder;
 24  
 import org.apache.commons.cli.Options;
 25  
 import org.apache.commons.cli.PosixParser;
 26  
 import org.apache.maven.settings.io.xpp3.SettingsXpp3Writer;
 27  
 import org.apache.maven.shared.invoker.DefaultInvocationRequest;
 28  
 import org.apache.maven.shared.invoker.DefaultInvoker;
 29  
 import org.apache.maven.shared.invoker.InvocationOutputHandler;
 30  
 import org.apache.maven.shared.invoker.InvocationRequest;
 31  
 import org.apache.maven.shared.invoker.InvocationResult;
 32  
 import org.apache.maven.shared.invoker.Invoker;
 33  
 import org.apache.maven.shared.invoker.InvokerLogger;
 34  
 import org.apache.maven.shared.invoker.MavenInvocationException;
 35  
 import org.apache.maven.shared.release.ReleaseResult;
 36  
 import org.apache.maven.shared.release.env.ReleaseEnvironment;
 37  
 import org.codehaus.plexus.logging.Logger;
 38  
 import org.codehaus.plexus.util.IOUtil;
 39  
 import org.codehaus.plexus.util.StringUtils;
 40  
 import org.codehaus.plexus.util.cli.CommandLineUtils;
 41  
 
 42  
 import java.io.File;
 43  
 import java.io.FileWriter;
 44  
 import java.io.IOException;
 45  
 import java.util.ArrayList;
 46  
 import java.util.List;
 47  
 import java.util.Properties;
 48  
 import java.util.StringTokenizer;
 49  
 
 50  
 /**
 51  
  * Fork Maven using the maven-invoker shared library.
 52  
  *
 53  
  * @plexus.component role="org.apache.maven.shared.release.exec.MavenExecutor" role-hint="invoker"
 54  
  */
 55  82
 public class InvokerMavenExecutor
 56  
     extends AbstractMavenExecutor
 57  
 {
 58  
 
 59  2
     private static final Options OPTIONS = new Options();
 60  
 
 61  
     private static final char SET_SYSTEM_PROPERTY = 'D';
 62  
 
 63  
     private static final char OFFLINE = 'o';
 64  
 
 65  
     private static final char REACTOR = 'r';
 66  
 
 67  
     private static final char QUIET = 'q';
 68  
 
 69  
     private static final char DEBUG = 'X';
 70  
 
 71  
     private static final char ERRORS = 'e';
 72  
 
 73  
     private static final char NON_RECURSIVE = 'N';
 74  
 
 75  
     private static final char UPDATE_SNAPSHOTS = 'U';
 76  
 
 77  
     private static final char ACTIVATE_PROFILES = 'P';
 78  
 
 79  
     private static final String FORCE_PLUGIN_UPDATES = "cpu";
 80  
 
 81  
     private static final String FORCE_PLUGIN_UPDATES2 = "up";
 82  
 
 83  
     private static final String SUPPRESS_PLUGIN_UPDATES = "npu";
 84  
 
 85  
     private static final String SUPPRESS_PLUGIN_REGISTRY = "npr";
 86  
 
 87  
     private static final char CHECKSUM_FAILURE_POLICY = 'C';
 88  
 
 89  
     private static final char CHECKSUM_WARNING_POLICY = 'c';
 90  
 
 91  
     private static final char ALTERNATE_USER_SETTINGS = 's';
 92  
 
 93  
     private static final String FAIL_FAST = "ff";
 94  
 
 95  
     private static final String FAIL_AT_END = "fae";
 96  
 
 97  
     private static final String FAIL_NEVER = "fn";
 98  
     
 99  
     private static final String ALTERNATE_POM_FILE = "f";
 100  
 
 101  
     static
 102  
     {
 103  2
         OPTIONS.addOption(
 104  
             OptionBuilder.withLongOpt( "define" ).hasArg().withDescription( "Define a system property" ).create(
 105  
                 SET_SYSTEM_PROPERTY ) );
 106  
 
 107  2
         OPTIONS.addOption( OptionBuilder.withLongOpt( "offline" ).withDescription( "Work offline" ).create( OFFLINE ) );
 108  
 
 109  2
         OPTIONS.addOption(
 110  
             OptionBuilder.withLongOpt( "quiet" ).withDescription( "Quiet output - only show errors" ).create( QUIET ) );
 111  
 
 112  2
         OPTIONS.addOption(
 113  
             OptionBuilder.withLongOpt( "debug" ).withDescription( "Produce execution debug output" ).create( DEBUG ) );
 114  
 
 115  2
         OPTIONS.addOption(
 116  
             OptionBuilder.withLongOpt( "errors" ).withDescription( "Produce execution error messages" ).create(
 117  
                 ERRORS ) );
 118  
 
 119  2
         OPTIONS.addOption( OptionBuilder.withLongOpt( "reactor" ).withDescription(
 120  
             "Execute goals for project found in the reactor" ).create( REACTOR ) );
 121  
 
 122  2
         OPTIONS.addOption(
 123  
             OptionBuilder.withLongOpt( "non-recursive" ).withDescription( "Do not recurse into sub-projects" ).create(
 124  
                 NON_RECURSIVE ) );
 125  
 
 126  2
         OPTIONS.addOption( OptionBuilder.withLongOpt( "update-snapshots" ).withDescription(
 127  
             "Forces a check for updated releases and snapshots on remote repositories" ).create( UPDATE_SNAPSHOTS ) );
 128  
 
 129  2
         OPTIONS.addOption( OptionBuilder.withLongOpt( "activate-profiles" ).withDescription(
 130  
             "Comma-delimited list of profiles to activate" ).hasArg().create( ACTIVATE_PROFILES ) );
 131  
 
 132  2
         OPTIONS.addOption( OptionBuilder.withLongOpt( "check-plugin-updates" ).withDescription(
 133  
             "Force upToDate check for any relevant registered plugins" ).create( FORCE_PLUGIN_UPDATES ) );
 134  
 
 135  2
         OPTIONS.addOption( OptionBuilder.withLongOpt( "update-plugins" ).withDescription(
 136  
             "Synonym for " + FORCE_PLUGIN_UPDATES ).create( FORCE_PLUGIN_UPDATES2 ) );
 137  
 
 138  2
         OPTIONS.addOption( OptionBuilder.withLongOpt( "no-plugin-updates" ).withDescription(
 139  
             "Suppress upToDate check for any relevant registered plugins" ).create( SUPPRESS_PLUGIN_UPDATES ) );
 140  
 
 141  2
         OPTIONS.addOption( OptionBuilder.withLongOpt( "no-plugin-registry" ).withDescription(
 142  
             "Don't use ~/.m2/plugin-registry.xml for plugin versions" ).create( SUPPRESS_PLUGIN_REGISTRY ) );
 143  
 
 144  2
         OPTIONS.addOption( OptionBuilder.withLongOpt( "strict-checksums" ).withDescription(
 145  
             "Fail the build if checksums don't match" ).create( CHECKSUM_FAILURE_POLICY ) );
 146  
 
 147  2
         OPTIONS.addOption(
 148  
             OptionBuilder.withLongOpt( "lax-checksums" ).withDescription( "Warn if checksums don't match" ).create(
 149  
                 CHECKSUM_WARNING_POLICY ) );
 150  
 
 151  2
         OPTIONS.addOption( OptionBuilder.withLongOpt( "settings" ).withDescription(
 152  
             "Alternate path for the user settings file" ).hasArg().create( ALTERNATE_USER_SETTINGS ) );
 153  
 
 154  2
         OPTIONS.addOption( OptionBuilder.withLongOpt( "fail-fast" ).withDescription(
 155  
             "Stop at first failure in reactorized builds" ).create( FAIL_FAST ) );
 156  
 
 157  2
         OPTIONS.addOption( OptionBuilder.withLongOpt( "fail-at-end" ).withDescription(
 158  
             "Only fail the build afterwards; allow all non-impacted builds to continue" ).create( FAIL_AT_END ) );
 159  
 
 160  2
         OPTIONS.addOption( OptionBuilder.withLongOpt( "fail-never" ).withDescription(
 161  
             "NEVER fail the build, regardless of project result" ).create( FAIL_NEVER ) );
 162  
         
 163  2
         OPTIONS.addOption( OptionBuilder.withLongOpt( "file" ).withDescription( 
 164  
             "Force the use of an alternate POM file." ).hasArg().create( ALTERNATE_POM_FILE ) );
 165  2
     }
 166  
 
 167  
     // TODO: Configuring an invocation request from a command line could as well be part of the Invoker API
 168  
     private void setupRequest( InvocationRequest req,
 169  
                                LoggerBridge bridge,
 170  
                                String additionalArguments )
 171  
         throws MavenExecutorException
 172  
     {
 173  
         try
 174  
         {
 175  0
             String[] args = CommandLineUtils.translateCommandline( additionalArguments );
 176  0
             CommandLine cli = new PosixParser().parse( OPTIONS, args );
 177  
 
 178  0
             if ( cli.hasOption( SET_SYSTEM_PROPERTY ) )
 179  
             {
 180  0
                 String[] properties = cli.getOptionValues( SET_SYSTEM_PROPERTY );
 181  0
                 Properties props = new Properties();
 182  0
                 for ( int i = 0; i < properties.length; i++ )
 183  
                 {
 184  0
                     String property = properties[i];
 185  
                     String name, value;
 186  0
                     int sep = property.indexOf( "=" );
 187  0
                     if ( sep <= 0 )
 188  
                     {
 189  0
                         name = property.trim();
 190  0
                         value = "true";
 191  
                     }
 192  
                     else
 193  
                     {
 194  0
                         name = property.substring( 0, sep ).trim();
 195  0
                         value = property.substring( sep + 1 ).trim();
 196  
                     }
 197  0
                     props.setProperty( name, value );
 198  
                 }
 199  
 
 200  0
                 req.setProperties( props );
 201  
             }
 202  
 
 203  0
             if ( cli.hasOption( OFFLINE ) )
 204  
             {
 205  0
                 req.setOffline( true );
 206  
             }
 207  
 
 208  0
             if ( cli.hasOption( QUIET ) )
 209  
             {
 210  
                 // TODO: setQuiet() currently not supported by InvocationRequest
 211  0
                 req.setDebug( false );
 212  
             }
 213  0
             else if ( cli.hasOption( DEBUG ) )
 214  
             {
 215  0
                 req.setDebug( true );
 216  
             }
 217  0
             else if ( cli.hasOption( ERRORS ) )
 218  
             {
 219  0
                 req.setShowErrors( true );
 220  
             }
 221  
 
 222  0
             if ( cli.hasOption( REACTOR ) )
 223  
             {
 224  0
                 req.setRecursive( true );
 225  
             }
 226  0
             else if ( cli.hasOption( NON_RECURSIVE ) )
 227  
             {
 228  0
                 req.setRecursive( false );
 229  
             }
 230  
 
 231  0
             if ( cli.hasOption( UPDATE_SNAPSHOTS ) )
 232  
             {
 233  0
                 req.setUpdateSnapshots( true );
 234  
             }
 235  
 
 236  0
             if ( cli.hasOption( ACTIVATE_PROFILES ) )
 237  
             {
 238  0
                 String[] profiles = cli.getOptionValues( ACTIVATE_PROFILES );
 239  0
                 List<String> activatedProfiles = new ArrayList<String>();
 240  0
                 List<String> deactivatedProfiles = new ArrayList<String>();
 241  
 
 242  0
                 if ( profiles != null )
 243  
                 {
 244  0
                     for ( int i = 0; i < profiles.length; ++i )
 245  
                     {
 246  0
                         StringTokenizer profileTokens = new StringTokenizer( profiles[i], "," );
 247  
 
 248  0
                         while ( profileTokens.hasMoreTokens() )
 249  
                         {
 250  0
                             String profileAction = profileTokens.nextToken().trim();
 251  
 
 252  0
                             if ( profileAction.startsWith( "-" ) || profileAction.startsWith( "!" ) )
 253  
                             {
 254  0
                                 deactivatedProfiles.add( profileAction.substring( 1 ) );
 255  
                             }
 256  0
                             else if ( profileAction.startsWith( "+" ) )
 257  
                             {
 258  0
                                 activatedProfiles.add( profileAction.substring( 1 ) );
 259  
                             }
 260  
                             else
 261  
                             {
 262  0
                                 activatedProfiles.add( profileAction );
 263  
                             }
 264  0
                         }
 265  
                     }
 266  
                 }
 267  
 
 268  0
                 if ( !deactivatedProfiles.isEmpty() )
 269  
                 {
 270  0
                     getLogger().warn( "Explicit profile deactivation is not yet supported. "
 271  
                                           + "The following profiles will NOT be deactivated: " + StringUtils.join(
 272  
                         deactivatedProfiles.iterator(), ", " ) );
 273  
                 }
 274  
 
 275  0
                 if ( !activatedProfiles.isEmpty() )
 276  
                 {
 277  0
                     req.setProfiles( activatedProfiles );
 278  
                 }
 279  
             }
 280  
 
 281  0
             if ( cli.hasOption( FORCE_PLUGIN_UPDATES ) || cli.hasOption( FORCE_PLUGIN_UPDATES2 ) )
 282  
             {
 283  0
                 getLogger().warn( "Forcing plugin updates is not supported currently." );
 284  
             }
 285  0
             else if ( cli.hasOption( SUPPRESS_PLUGIN_UPDATES ) )
 286  
             {
 287  0
                 req.setNonPluginUpdates( true );
 288  
             }
 289  
 
 290  0
             if ( cli.hasOption( SUPPRESS_PLUGIN_REGISTRY ) )
 291  
             {
 292  0
                 getLogger().warn( "Explicit suppression of the plugin registry is not supported currently." );
 293  
             }
 294  
 
 295  0
             if ( cli.hasOption( CHECKSUM_FAILURE_POLICY ) )
 296  
             {
 297  0
                 req.setGlobalChecksumPolicy( InvocationRequest.CHECKSUM_POLICY_FAIL );
 298  
             }
 299  0
             else if ( cli.hasOption( CHECKSUM_WARNING_POLICY ) )
 300  
             {
 301  0
                 req.setGlobalChecksumPolicy( InvocationRequest.CHECKSUM_POLICY_WARN );
 302  
             }
 303  
 
 304  0
             if ( cli.hasOption( ALTERNATE_USER_SETTINGS ) )
 305  
             {
 306  0
                 req.setUserSettingsFile( new File( cli.getOptionValue( ALTERNATE_USER_SETTINGS ) ) );
 307  
             }
 308  
 
 309  0
             if ( cli.hasOption( FAIL_AT_END ) )
 310  
             {
 311  0
                 req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_AT_END );
 312  
             }
 313  0
             else if ( cli.hasOption( FAIL_FAST ) )
 314  
             {
 315  0
                 req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_FAST );
 316  
             }
 317  0
             if ( cli.hasOption( FAIL_NEVER ) )
 318  
             {
 319  0
                 req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_NEVER );
 320  
             }
 321  0
             if ( cli.hasOption( ALTERNATE_POM_FILE ) )
 322  
             {
 323  0
                 if ( req.getPomFileName() != null )
 324  
                 {
 325  0
                     getLogger().info( "pomFileName is already set, ignoring the -f argument" );
 326  
                 }
 327  
                 else
 328  
                 {
 329  0
                     req.setPomFileName( cli.getOptionValue( ALTERNATE_POM_FILE ) );
 330  
                 }
 331  
             }
 332  
         }
 333  0
         catch ( Exception e )
 334  
         {
 335  0
             throw new MavenExecutorException( "Failed to re-parse additional arguments for Maven invocation.", e );
 336  0
         }
 337  0
     }
 338  
 
 339  
     @Override
 340  
     public void executeGoals( File workingDirectory, List<String> goals, ReleaseEnvironment releaseEnvironment,
 341  
                               boolean interactive, String additionalArguments, String pomFileName,
 342  
                               ReleaseResult result )
 343  
         throws MavenExecutorException
 344  
     {
 345  0
         Handler handler = new Handler( getLogger() );
 346  0
         LoggerBridge bridge = new LoggerBridge( getLogger() );
 347  
 
 348  0
         Invoker invoker =
 349  
             new DefaultInvoker().setMavenHome( releaseEnvironment.getMavenHome() ).setLogger( bridge ).setOutputHandler(
 350  
                 handler ).setErrorHandler( handler );
 351  
 
 352  0
         InvocationRequest req =
 353  
             new DefaultInvocationRequest().setDebug( getLogger().isDebugEnabled() ).setBaseDirectory(
 354  
                 workingDirectory ).setInteractive( interactive );
 355  
 
 356  0
         if ( pomFileName != null )
 357  
         {
 358  0
             req.setPomFileName( pomFileName );
 359  
         }
 360  
 
 361  0
         File settingsFile = null;
 362  0
         if ( releaseEnvironment.getSettings() != null )
 363  
         {
 364  
             // Have to serialize to a file as if Maven is embedded, there may not actually be a settings.xml on disk
 365  
             try
 366  
             {
 367  0
                 settingsFile = File.createTempFile( "release-settings", ".xml" );
 368  0
                 SettingsXpp3Writer writer = new SettingsXpp3Writer();
 369  0
                 FileWriter fileWriter = null;
 370  
                 try
 371  
                 {
 372  0
                     fileWriter = new FileWriter( settingsFile );
 373  0
                     writer.write( fileWriter, releaseEnvironment.getSettings() );
 374  
                 }
 375  
                 finally
 376  
                 {
 377  0
                     IOUtil.close( fileWriter );
 378  0
                 }
 379  0
                 req.setUserSettingsFile( settingsFile );
 380  
             }
 381  0
             catch ( IOException e )
 382  
             {
 383  0
                 throw new MavenExecutorException( "Could not create temporary file for release settings.xml", e );
 384  0
             }
 385  
         }
 386  
         try
 387  
         {
 388  0
             File localRepoDir = releaseEnvironment.getLocalRepositoryDirectory();
 389  0
             if ( localRepoDir != null )
 390  
             {
 391  0
                 req.setLocalRepositoryDirectory( localRepoDir );
 392  
             }
 393  
 
 394  0
             setupRequest( req, bridge, additionalArguments );
 395  
 
 396  0
             req.setGoals( goals );
 397  
 
 398  
             try
 399  
             {
 400  0
                 InvocationResult invocationResult = invoker.execute( req );
 401  
 
 402  0
                 if ( invocationResult.getExecutionException() != null )
 403  
                 {
 404  0
                     throw new MavenExecutorException( "Error executing Maven.",
 405  
                                                       invocationResult.getExecutionException() );
 406  
                 }
 407  0
                 if ( invocationResult.getExitCode() != 0 )
 408  
                 {
 409  0
                     throw new MavenExecutorException(
 410  
                         "Maven execution failed, exit code: \'" + invocationResult.getExitCode() + "\'",
 411  
                         invocationResult.getExitCode(), "", "" );
 412  
                 }
 413  
             }
 414  0
             catch ( MavenInvocationException e )
 415  
             {
 416  0
                 throw new MavenExecutorException( "Failed to invoke Maven build.", e );
 417  0
             }
 418  
         }
 419  
         finally
 420  
         {
 421  0
             if ( settingsFile != null && settingsFile.exists() && !settingsFile.delete() )
 422  
             {
 423  0
                 settingsFile.deleteOnExit();
 424  
             }
 425  
         }
 426  0
     }
 427  
 
 428  
     private static final class Handler
 429  
         implements InvocationOutputHandler
 430  
     {
 431  
         private Logger logger;
 432  
 
 433  
         Handler( Logger logger )
 434  0
         {
 435  0
             this.logger = logger;
 436  0
         }
 437  
 
 438  
         public void consumeLine( String line )
 439  
         {
 440  0
             logger.info( line );
 441  0
         }
 442  
     }
 443  
 
 444  82
     private static final class LoggerBridge
 445  
         implements InvokerLogger
 446  
     {
 447  
 
 448  
         private Logger logger;
 449  
 
 450  
         LoggerBridge( Logger logger )
 451  0
         {
 452  0
             this.logger = logger;
 453  0
         }
 454  
 
 455  
         public void debug( String message, Throwable error )
 456  
         {
 457  0
             logger.debug( message, error );
 458  0
         }
 459  
 
 460  
         public void debug( String message )
 461  
         {
 462  0
             logger.debug( message );
 463  0
         }
 464  
 
 465  
         public void error( String message, Throwable error )
 466  
         {
 467  0
             logger.error( message, error );
 468  0
         }
 469  
 
 470  
         public void error( String message )
 471  
         {
 472  0
             logger.error( message );
 473  0
         }
 474  
 
 475  
         public void fatalError( String message, Throwable error )
 476  
         {
 477  0
             logger.fatalError( message, error );
 478  0
         }
 479  
 
 480  
         public void fatalError( String message )
 481  
         {
 482  0
             logger.fatalError( message );
 483  0
         }
 484  
 
 485  
         public Logger getChildLogger( String message )
 486  
         {
 487  0
             return logger.getChildLogger( message );
 488  
         }
 489  
 
 490  
         public String getName()
 491  
         {
 492  0
             return logger.getName();
 493  
         }
 494  
 
 495  
         public int getThreshold()
 496  
         {
 497  0
             return logger.getThreshold();
 498  
         }
 499  
 
 500  
         public void info( String message, Throwable error )
 501  
         {
 502  0
             logger.info( message, error );
 503  0
         }
 504  
 
 505  
         public void info( String message )
 506  
         {
 507  0
             logger.info( message );
 508  0
         }
 509  
 
 510  
         public boolean isDebugEnabled()
 511  
         {
 512  0
             return logger.isDebugEnabled();
 513  
         }
 514  
 
 515  
         public boolean isErrorEnabled()
 516  
         {
 517  0
             return logger.isErrorEnabled();
 518  
         }
 519  
 
 520  
         public boolean isFatalErrorEnabled()
 521  
         {
 522  0
             return logger.isFatalErrorEnabled();
 523  
         }
 524  
 
 525  
         public boolean isInfoEnabled()
 526  
         {
 527  0
             return logger.isInfoEnabled();
 528  
         }
 529  
 
 530  
         public boolean isWarnEnabled()
 531  
         {
 532  0
             return logger.isWarnEnabled();
 533  
         }
 534  
 
 535  
         public void setThreshold( int level )
 536  
         {
 537  
             // NOTE:
 538  
             // logger.setThreadhold( level )
 539  
             // is not supported in plexus-container-default:1.0-alpha-9 as used in Maven 2.x
 540  0
         }
 541  
 
 542  
         public void warn( String message, Throwable error )
 543  
         {
 544  0
             logger.warn( message, error );
 545  0
         }
 546  
 
 547  
         public void warn( String message )
 548  
         {
 549  0
             logger.warn( message );
 550  0
         }
 551  
 
 552  
     }
 553  
 
 554  
 }