Coverage Report - org.apache.maven.archetype.mojos.IntegrationTestMojo
 
Classes in this File Line Coverage Branch Coverage Complexity
IntegrationTestMojo
0%
0/176
0%
0/62
4
IntegrationTestMojo$CompositeMap
0%
0/38
0%
0/32
4
IntegrationTestMojo$IntegrationTestFailure
0%
0/12
N/A
4
 
 1  
 package org.apache.maven.archetype.mojos;
 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.collections.CollectionUtils;
 23  
 import org.apache.maven.archetype.ArchetypeGenerationRequest;
 24  
 import org.apache.maven.archetype.ArchetypeGenerationResult;
 25  
 import org.apache.maven.archetype.common.Constants;
 26  
 import org.apache.maven.archetype.exception.ArchetypeNotConfigured;
 27  
 import org.apache.maven.archetype.generator.ArchetypeGenerator;
 28  
 import org.apache.maven.plugin.AbstractMojo;
 29  
 import org.apache.maven.plugin.MojoExecutionException;
 30  
 import org.apache.maven.plugin.MojoFailureException;
 31  
 import org.apache.maven.project.MavenProject;
 32  
 import org.apache.maven.settings.Settings;
 33  
 import org.apache.maven.shared.invoker.DefaultInvocationRequest;
 34  
 import org.apache.maven.shared.invoker.InvocationRequest;
 35  
 import org.apache.maven.shared.invoker.InvocationResult;
 36  
 import org.apache.maven.shared.invoker.Invoker;
 37  
 import org.apache.maven.shared.invoker.MavenInvocationException;
 38  
 import org.apache.maven.shared.scriptinterpreter.RunFailureException;
 39  
 import org.apache.maven.shared.scriptinterpreter.ScriptRunner;
 40  
 import org.codehaus.plexus.util.FileUtils;
 41  
 import org.codehaus.plexus.util.IOUtil;
 42  
 import org.codehaus.plexus.util.InterpolationFilterReader;
 43  
 import org.codehaus.plexus.util.ReaderFactory;
 44  
 import org.codehaus.plexus.util.StringUtils;
 45  
 import org.codehaus.plexus.util.WriterFactory;
 46  
 import org.codehaus.plexus.util.introspection.ReflectionValueExtractor;
 47  
 
 48  
 import java.io.File;
 49  
 import java.io.FileInputStream;
 50  
 import java.io.FileNotFoundException;
 51  
 import java.io.IOException;
 52  
 import java.io.InputStream;
 53  
 import java.io.Reader;
 54  
 import java.io.StringWriter;
 55  
 import java.io.Writer;
 56  
 import java.util.Arrays;
 57  
 import java.util.Collection;
 58  
 import java.util.HashMap;
 59  
 import java.util.LinkedHashMap;
 60  
 import java.util.List;
 61  
 import java.util.Map;
 62  
 import java.util.Properties;
 63  
 import java.util.Set;
 64  
 
 65  
 /**
 66  
  * <p>Execute the archetype integration tests, consisting in generating projects from the current archetype and
 67  
  * optionally comparing generated projects with reference copy.</p>
 68  
  * <p/>
 69  
  * <p>Each IT consists of a sub-directory in <code>src/test/resources/projects</code> containing:</p>
 70  
  * <ul>
 71  
  * <li>a <code>goal.txt</code> file, containing a list of goals to run against the generated project (can be empty,
 72  
  * content ignored before maven-archetype-plugin 2.1),</li>
 73  
  * <li>an <code>archetype.properties</code> file, containing properties for project generation,</li>
 74  
  * <li>an optional <code>reference/</code> directory containing a reference copy of the expected project created from the IT.</li>
 75  
  * </ul>
 76  
  * <p/>
 77  
  * Notice that it is expected to be run as part as of a build after the <code>package</code> phase and not directly
 78  
  * as a goal from CLI.
 79  
  *
 80  
  * @author rafale
 81  
  * @requiresProject true
 82  
  * @goal integration-test
 83  
  */
 84  0
 public class IntegrationTestMojo
 85  
     extends AbstractMojo
 86  
 {
 87  
     /**
 88  
      * @component
 89  
      */
 90  
     private ArchetypeGenerator archetypeGenerator;
 91  
 
 92  
     /**
 93  
      * @component
 94  
      */
 95  
     private Invoker invoker;
 96  
 
 97  
     /**
 98  
      * The archetype project to execute the integration tests on.
 99  
      *
 100  
      * @parameter expression="${project}"
 101  
      * @required
 102  
      * @readonly
 103  
      */
 104  
     private MavenProject project;
 105  
 
 106  
     /**
 107  
      * Skip the integration test.
 108  
      *
 109  
      * @parameter expression="${archetype.test.skip}"
 110  
      * @readonly
 111  
      */
 112  0
     private boolean skip = false;
 113  
 
 114  
 
 115  
     /**
 116  
      * Directory of test projects
 117  
      *
 118  
      * @parameter expression="${archetype.test.projectsDirectory}" default-value="${project.build.testOutputDirectory}/projects"
 119  
      * @required
 120  
      * @since 2.2
 121  
      */
 122  
     private File testProjectsDirectory;
 123  
 
 124  
     /**
 125  
      * Relative path of a cleanup/verification hook script to run after executing the build. This script may be written
 126  
      * with either BeanShell or Groovy. If the file extension is omitted (e.g. <code>verify</code>), the
 127  
      * plugin searches for the file by trying out the well-known extensions <code>.bsh</code> and <code>.groovy</code>.
 128  
      * If this script exists for a particular project but returns any non-null value different from <code>true</code> or
 129  
      * throws an exception, the corresponding build is flagged as a failure.
 130  
      *
 131  
      * @parameter expression="${archetype.test.verifyScript}" default-value="verify"
 132  
      * @since 2.2
 133  
      */
 134  
     private String postBuildHookScript;
 135  
 
 136  
     /**
 137  
      * Suppress logging to the <code>build.log</code> file.
 138  
      *
 139  
      * @parameter expression="${archetype.test.noLog}" default-value="false"
 140  
      * @since 2.2
 141  
      */
 142  
     private boolean noLog;
 143  
 
 144  
     /**
 145  
      * Flag used to determine whether the build logs should be output to the normal mojo log.
 146  
      *
 147  
      * @parameter expression="${archetype.test.streamLogs}" default-value="true"
 148  
      * @since 2.2
 149  
      */
 150  
     private boolean streamLogs;
 151  
 
 152  
     /**
 153  
      * The file encoding for the post-build script.
 154  
      *
 155  
      * @parameter expression="${encoding}" default-value="${project.build.sourceEncoding}"
 156  
      * @since 2.2
 157  
      */
 158  
     private String encoding;
 159  
 
 160  
     /**
 161  
      * The local repository to run maven instance.
 162  
      *
 163  
      * @parameter expression="${archetype.test.localRepositoryPath}" default-value="${settings.localRepository}"
 164  
      * @required
 165  
      * @readonly
 166  
      * @since 2.2
 167  
      */
 168  
     private File localRepositoryPath;
 169  
 
 170  
     /**
 171  
      * flag to enable show mvn version used for running its (cli option : -V,--show-version )
 172  
      *
 173  
      * @parameter expression="${archetype.test.showVersion}" default-value="false"
 174  
      * @since 2.2
 175  
      */
 176  
     private boolean showVersion;
 177  
 
 178  
     /**
 179  
      * Whether to show debug statements in the build output.
 180  
      *
 181  
      * @parameter expression="${archetype.test.debug}" default-value="false"
 182  
      * @since 2.2
 183  
      */
 184  
     private boolean debug;
 185  
 
 186  
     /**
 187  
      * A list of additional properties which will be used to filter tokens in settings.xml
 188  
      *
 189  
      * @parameter
 190  
      * @since 2.2
 191  
      */
 192  
     private Map<String, String> filterProperties;
 193  
 
 194  
     /**
 195  
      * The current user system settings for use in Maven.
 196  
      *
 197  
      * @parameter expression="${settings}"
 198  
      * @required
 199  
      * @readonly
 200  
      * @since 2.2
 201  
      */
 202  
     private Settings settings;
 203  
 
 204  
     /**
 205  
      * Path to an alternate <code>settings.xml</code> to use for Maven invocation with all ITs. Note that the
 206  
      * <code>&lt;localRepository&gt;</code> element of this settings file is always ignored, i.e. the path given by the
 207  
      * parameter {@link #localRepositoryPath} is dominant.
 208  
      *
 209  
      * @parameter expression="${archetype.test.settingsFile}"
 210  
      * @since 2.2
 211  
      */
 212  
     private File settingsFile;
 213  
 
 214  
 
 215  
     public void execute()
 216  
         throws MojoExecutionException, MojoFailureException
 217  
     {
 218  0
         if ( skip )
 219  
         {
 220  0
             return;
 221  
         }
 222  
 
 223  0
         if ( !testProjectsDirectory.exists() )
 224  
         {
 225  0
             getLog().warn( "No Archetype IT projects: root 'projects' directory not found." );
 226  
 
 227  0
             return;
 228  
         }
 229  
 
 230  0
         File archetypeFile = project.getArtifact().getFile();
 231  
 
 232  0
         if ( archetypeFile == null )
 233  
         {
 234  0
             throw new MojoFailureException( "Unable to get the archetypes' artifact which should have just been built:"
 235  
                                                 + " you probably launched 'mvn archetype:integration-test' instead of"
 236  
                                                 + " 'mvn integration-test'." );
 237  
         }
 238  
 
 239  
         try
 240  
         {
 241  0
             @SuppressWarnings( "unchecked" ) List<File> projectsGoalFiles =
 242  
                 FileUtils.getFiles( testProjectsDirectory, "*/goal.txt", "" );
 243  
 
 244  0
             if ( projectsGoalFiles.size() == 0 )
 245  
             {
 246  0
                 getLog().warn( "No Archetype IT projects: no directory with goal.txt found." );
 247  
 
 248  0
                 return;
 249  
             }
 250  
 
 251  0
             StringWriter errorWriter = new StringWriter();
 252  0
             for ( File goalFile : projectsGoalFiles )
 253  
             {
 254  
                 try
 255  
                 {
 256  0
                     processIntegrationTest( goalFile, archetypeFile );
 257  
                 }
 258  0
                 catch ( IntegrationTestFailure ex )
 259  
                 {
 260  0
                     errorWriter.write( "\nArchetype IT '" + goalFile.getParentFile().getName() + "' failed: " );
 261  0
                     errorWriter.write( ex.getMessage() );
 262  0
                 }
 263  
             }
 264  
 
 265  0
             String errors = errorWriter.toString();
 266  0
             if ( !StringUtils.isEmpty( errors ) )
 267  
             {
 268  0
                 throw new MojoExecutionException( errors );
 269  
             }
 270  
         }
 271  0
         catch ( IOException ex )
 272  
         {
 273  0
             throw new MojoFailureException( ex, ex.getMessage(), ex.getMessage() );
 274  0
         }
 275  0
     }
 276  
 
 277  
     /**
 278  
      * Checks that actual directory content is the same as reference.
 279  
      *
 280  
      * @param reference the reference directory
 281  
      * @param actual    the actual directory to compare with the reference
 282  
      * @throws IntegrationTestFailure if content differs
 283  
      */
 284  
     private void assertDirectoryEquals( File reference, File actual )
 285  
         throws IntegrationTestFailure, IOException
 286  
     {
 287  0
         @SuppressWarnings( "unchecked" ) List<String> referenceFiles =
 288  
             FileUtils.getFileAndDirectoryNames( reference, "**", null, false, true, true, true );
 289  0
         getLog().debug( "reference content: " + referenceFiles );
 290  
 
 291  0
         @SuppressWarnings( "unchecked" ) List<String> actualFiles =
 292  
             FileUtils.getFileAndDirectoryNames( actual, "**", null, false, true, true, true );
 293  0
         getLog().debug( "actual content: " + referenceFiles );
 294  
 
 295  0
         boolean fileNamesEquals = CollectionUtils.isEqualCollection( referenceFiles, actualFiles );
 296  
 
 297  0
         if ( !fileNamesEquals )
 298  
         {
 299  0
             getLog().debug( "Actual list of files is not the same as reference:" );
 300  0
             int missing = 0;
 301  0
             for ( String ref : referenceFiles )
 302  
             {
 303  0
                 if ( actualFiles.contains( ref ) )
 304  
                 {
 305  0
                     actualFiles.remove( ref );
 306  0
                     getLog().debug( "Contained " + ref );
 307  
                 }
 308  
                 else
 309  
                 {
 310  0
                     missing++;
 311  0
                     getLog().error( "Not contained " + ref );
 312  
                 }
 313  
             }
 314  0
             getLog().error( "Remains " + actualFiles );
 315  
 
 316  0
             throw new IntegrationTestFailure(
 317  
                 "Reference and generated project differs (missing: " + missing + ", unexpected: " + actualFiles.size()
 318  
                     + ")" );
 319  
         }
 320  
 
 321  0
         boolean contentEquals = true;
 322  
 
 323  0
         for ( String file : referenceFiles )
 324  
         {
 325  0
             File referenceFile = new File( reference, file );
 326  0
             File actualFile = new File( actual, file );
 327  
 
 328  0
             if ( referenceFile.isDirectory() )
 329  
             {
 330  0
                 if ( actualFile.isFile() )
 331  
                 {
 332  0
                     getLog().warn( "File " + file + " is a directory in the reference but a file in actual" );
 333  0
                     contentEquals = false;
 334  
                 }
 335  
             }
 336  0
             else if ( actualFile.isDirectory() )
 337  
             {
 338  0
                 if ( referenceFile.isFile() )
 339  
                 {
 340  0
                     getLog().warn( "File " + file + " is a file in the reference but a directory in actual" );
 341  0
                     contentEquals = false;
 342  
                 }
 343  
             }
 344  0
             else if ( !FileUtils.contentEquals( referenceFile, actualFile ) )
 345  
             {
 346  0
                 getLog().warn( "Contents of file " + file + " are not equal" );
 347  0
                 contentEquals = false;
 348  
             }
 349  0
         }
 350  0
         if ( !contentEquals )
 351  
         {
 352  0
             throw new IntegrationTestFailure( "Some content are not equals" );
 353  
         }
 354  0
     }
 355  
 
 356  
     private Properties loadProperties( final File propertiesFile )
 357  
         throws IOException, FileNotFoundException
 358  
     {
 359  0
         Properties properties = new Properties();
 360  
 
 361  0
         InputStream in = null;
 362  
         try
 363  
         {
 364  0
             in = new FileInputStream( propertiesFile );
 365  
 
 366  0
             properties.load( in );
 367  
         }
 368  
         finally
 369  
         {
 370  0
             IOUtil.close( in );
 371  0
         }
 372  
 
 373  0
         return properties;
 374  
     }
 375  
 
 376  
     private void processIntegrationTest( File goalFile, File archetypeFile )
 377  
         throws IntegrationTestFailure, MojoExecutionException
 378  
     {
 379  0
         getLog().info( "Processing Archetype IT project: " + goalFile.getParentFile().getName() );
 380  
 
 381  
         try
 382  
         {
 383  0
             Properties properties = getProperties( goalFile );
 384  
 
 385  0
             String basedir = goalFile.getParentFile().getPath() + "/project";
 386  
 
 387  0
             FileUtils.deleteDirectory( basedir );
 388  
 
 389  0
             FileUtils.mkdir( basedir );
 390  
 
 391  0
             ArchetypeGenerationRequest request =
 392  
                 new ArchetypeGenerationRequest().setArchetypeGroupId( project.getGroupId() ).setArchetypeArtifactId(
 393  
                     project.getArtifactId() ).setArchetypeVersion( project.getVersion() ).setGroupId(
 394  
                     properties.getProperty( Constants.GROUP_ID ) ).setArtifactId(
 395  
                     properties.getProperty( Constants.ARTIFACT_ID ) ).setVersion(
 396  
                     properties.getProperty( Constants.VERSION ) ).setPackage(
 397  
                     properties.getProperty( Constants.PACKAGE ) ).setOutputDirectory( basedir ).setProperties(
 398  
                     properties );
 399  
 
 400  0
             ArchetypeGenerationResult result = new ArchetypeGenerationResult();
 401  
 
 402  0
             archetypeGenerator.generateArchetype( request, archetypeFile, result );
 403  
 
 404  0
             if ( result.getCause() != null )
 405  
             {
 406  0
                 if ( result.getCause() instanceof ArchetypeNotConfigured )
 407  
                 {
 408  0
                     ArchetypeNotConfigured anc = (ArchetypeNotConfigured) result.getCause();
 409  
 
 410  0
                     throw new IntegrationTestFailure(
 411  
                         "Missing required properties in archetype.properties: " + StringUtils.join(
 412  
                             anc.getMissingProperties().iterator(), ", " ), anc );
 413  
                 }
 414  
 
 415  0
                 throw new IntegrationTestFailure( result.getCause().getMessage(), result.getCause() );
 416  
             }
 417  
 
 418  0
             File reference = new File( goalFile.getParentFile(), "reference" );
 419  
 
 420  0
             if ( reference.exists() )
 421  
             {
 422  
                 // compare generated project with reference
 423  0
                 getLog().info( "Comparing generated project with reference content: " + reference );
 424  
 
 425  0
                 assertDirectoryEquals( reference, new File( basedir, request.getArtifactId() ) );
 426  
             }
 427  
 
 428  0
             String goals = FileUtils.fileRead( goalFile );
 429  
 
 430  0
             invokePostArchetypeGenerationGoals( goals, new File( basedir, request.getArtifactId() ), goalFile );
 431  
         }
 432  0
         catch ( IOException ioe )
 433  
         {
 434  0
             throw new IntegrationTestFailure( ioe );
 435  0
         }
 436  0
     }
 437  
 
 438  
     private Properties getProperties( File goalFile )
 439  
         throws IOException
 440  
     {
 441  0
         File propertiesFile = new File( goalFile.getParentFile(), "archetype.properties" );
 442  
 
 443  0
         return loadProperties( propertiesFile );
 444  
     }
 445  
 
 446  
     private void invokePostArchetypeGenerationGoals( String goals, File basedir, File goalFile )
 447  
         throws IntegrationTestFailure, IOException, MojoExecutionException
 448  
     {
 449  0
         FileLogger logger = setupLogger( basedir );
 450  
 
 451  0
         if ( !StringUtils.isBlank( goals ) )
 452  
         {
 453  
 
 454  0
             getLog().info( "Invoking post-archetype-generation goals: " + goals );
 455  
 
 456  0
             if ( !localRepositoryPath.exists() )
 457  
             {
 458  0
                 localRepositoryPath.mkdirs();
 459  
             }
 460  
 
 461  0
             InvocationRequest request = new DefaultInvocationRequest().setBaseDirectory( basedir ).setGoals(
 462  
                 Arrays.asList( StringUtils.split( goals, "," ) ) ).setLocalRepositoryDirectory(
 463  
                 localRepositoryPath ).setInteractive( false ).setShowErrors( true );
 464  
 
 465  0
             request.setDebug( debug );
 466  
 
 467  0
             request.setShowVersion( showVersion );
 468  
 
 469  0
             if ( logger != null )
 470  
             {
 471  0
                 request.setErrorHandler( logger );
 472  
 
 473  0
                 request.setOutputHandler( logger );
 474  
             }
 475  
 
 476  0
             File interpolatedSettingsFile = null;
 477  0
             if ( settingsFile != null )
 478  
             {
 479  0
                 File interpolatedSettingsDirectory =
 480  
                     new File( project.getBuild().getOutputDirectory(), "archetype-it" );
 481  0
                 if ( interpolatedSettingsDirectory.exists() )
 482  
                 {
 483  0
                     FileUtils.deleteDirectory( interpolatedSettingsDirectory );
 484  
                 }
 485  0
                 interpolatedSettingsDirectory.mkdir();
 486  0
                 interpolatedSettingsFile =
 487  
                     new File( interpolatedSettingsDirectory, "interpolated-" + settingsFile.getName() );
 488  
 
 489  0
                 buildInterpolatedFile( settingsFile, interpolatedSettingsFile );
 490  
 
 491  0
                 request.setUserSettingsFile( interpolatedSettingsFile );
 492  
             }
 493  
 
 494  
             try
 495  
             {
 496  0
                 InvocationResult result = invoker.execute( request );
 497  
 
 498  0
                 getLog().info( "Post-archetype-generation invoker exit code: " + result.getExitCode() );
 499  
 
 500  0
                 if ( result.getExitCode() != 0 )
 501  
                 {
 502  0
                     throw new IntegrationTestFailure( "Execution failure: exit code = " + result.getExitCode(),
 503  
                                                       result.getExecutionException() );
 504  
                 }
 505  
             }
 506  0
             catch ( MavenInvocationException e )
 507  
             {
 508  0
                 throw new IntegrationTestFailure( "Cannot run additions goals.", e );
 509  0
             }
 510  0
         }
 511  
         else
 512  
         {
 513  0
             getLog().info( "No post-archetype-generation goals to invoke." );
 514  
         }
 515  
         // verify result
 516  0
         ScriptRunner scriptRunner = new ScriptRunner( getLog() );
 517  0
         scriptRunner.setScriptEncoding( encoding );
 518  
 
 519  0
         Map<String, Object> context = new LinkedHashMap<String, Object>();
 520  0
         context.put( "projectDir", basedir );
 521  
 
 522  
         try
 523  
         {
 524  0
             scriptRunner.run( "post-build script", goalFile.getParentFile(), postBuildHookScript, context, logger,
 525  
                               "failure post script", true );
 526  
         }
 527  0
         catch ( RunFailureException e )
 528  
         {
 529  0
             throw new IntegrationTestFailure( "post build script failure failure: " + e.getMessage(), e );
 530  0
         }
 531  0
     }
 532  
 
 533  
     private FileLogger setupLogger( File basedir )
 534  
         throws IOException
 535  
     {
 536  0
         FileLogger logger = null;
 537  
 
 538  0
         if ( !noLog )
 539  
         {
 540  0
             File outputLog = new File( basedir, "build.log" );
 541  
 
 542  0
             if ( streamLogs )
 543  
             {
 544  0
                 logger = new FileLogger( outputLog, getLog() );
 545  
             }
 546  
             else
 547  
             {
 548  0
                 logger = new FileLogger( outputLog );
 549  
             }
 550  
 
 551  0
             getLog().debug( "build log initialized in: " + outputLog );
 552  
 
 553  
         }
 554  
 
 555  0
         return logger;
 556  
     }
 557  
 
 558  
     class IntegrationTestFailure
 559  
         extends Exception
 560  
     {
 561  
         IntegrationTestFailure()
 562  0
         {
 563  0
             super();
 564  0
         }
 565  
 
 566  
         IntegrationTestFailure( String message )
 567  0
         {
 568  0
             super( message );
 569  0
         }
 570  
 
 571  
         IntegrationTestFailure( Throwable cause )
 572  0
         {
 573  0
             super( cause );
 574  0
         }
 575  
 
 576  
         IntegrationTestFailure( String message, Throwable cause )
 577  0
         {
 578  0
             super( message, cause );
 579  0
         }
 580  
     }
 581  
 
 582  
     /**
 583  
      * Returns the map-based value source used to interpolate settings and other stuff.
 584  
      *
 585  
      * @return The map-based value source for interpolation, never <code>null</code>.
 586  
      */
 587  
     private Map<String, Object> getInterpolationValueSource()
 588  
     {
 589  0
         Map<String, Object> props = new HashMap<String, Object>();
 590  0
         if ( filterProperties != null )
 591  
         {
 592  0
             props.putAll( filterProperties );
 593  
         }
 594  0
         if ( filterProperties != null )
 595  
         {
 596  0
             props.putAll( filterProperties );
 597  
         }
 598  0
         props.put( "basedir", this.project.getBasedir().getAbsolutePath() );
 599  0
         props.put( "baseurl", toUrl( this.project.getBasedir().getAbsolutePath() ) );
 600  0
         if ( settings.getLocalRepository() != null )
 601  
         {
 602  0
             props.put( "localRepository", settings.getLocalRepository() );
 603  0
             props.put( "localRepositoryUrl", toUrl( settings.getLocalRepository() ) );
 604  
         }
 605  0
         return new CompositeMap( this.project, props );
 606  
     }
 607  
 
 608  
     protected void buildInterpolatedFile( File originalFile, File interpolatedFile )
 609  
         throws MojoExecutionException
 610  
     {
 611  0
         getLog().debug( "Interpolate " + originalFile.getPath() + " to " + interpolatedFile.getPath() );
 612  
 
 613  
         try
 614  
         {
 615  
             String xml;
 616  
 
 617  0
             Reader reader = null;
 618  
             try
 619  
             {
 620  
                 // interpolation with token @...@
 621  0
                 Map<String, Object> composite = getInterpolationValueSource();
 622  0
                 reader = ReaderFactory.newXmlReader( originalFile );
 623  0
                 reader = new InterpolationFilterReader( reader, composite, "@", "@" );
 624  0
                 xml = IOUtil.toString( reader );
 625  
             }
 626  
             finally
 627  
             {
 628  0
                 IOUtil.close( reader );
 629  0
             }
 630  
 
 631  0
             Writer writer = null;
 632  
             try
 633  
             {
 634  0
                 interpolatedFile.getParentFile().mkdirs();
 635  0
                 writer = WriterFactory.newXmlWriter( interpolatedFile );
 636  0
                 writer.write( xml );
 637  0
                 writer.flush();
 638  
             }
 639  
             finally
 640  
             {
 641  0
                 IOUtil.close( writer );
 642  0
             }
 643  
         }
 644  0
         catch ( IOException e )
 645  
         {
 646  0
             throw new MojoExecutionException( "Failed to interpolate file " + originalFile.getPath(), e );
 647  0
         }
 648  0
     }
 649  
 
 650  0
     private static class CompositeMap
 651  
         implements Map<String, Object>
 652  
     {
 653  
 
 654  
         /**
 655  
          * The Maven project from which to extract interpolated values, never <code>null</code>.
 656  
          */
 657  
         private MavenProject mavenProject;
 658  
 
 659  
         /**
 660  
          * The set of additional properties from which to extract interpolated values, never <code>null</code>.
 661  
          */
 662  
         private Map<String, Object> properties;
 663  
 
 664  
         /**
 665  
          * Creates a new interpolation source backed by the specified Maven project and some user-specified properties.
 666  
          *
 667  
          * @param mavenProject The Maven project from which to extract interpolated values, must not be <code>null</code>.
 668  
          * @param properties   The set of additional properties from which to extract interpolated values, may be
 669  
          *                     <code>null</code>.
 670  
          */
 671  
         protected CompositeMap( MavenProject mavenProject, Map<String, Object> properties )
 672  0
         {
 673  0
             if ( mavenProject == null )
 674  
             {
 675  0
                 throw new IllegalArgumentException( "no project specified" );
 676  
             }
 677  0
             this.mavenProject = mavenProject;
 678  0
             this.properties = properties == null ? (Map) new Properties() : properties;
 679  0
         }
 680  
 
 681  
         /**
 682  
          * {@inheritDoc}
 683  
          *
 684  
          * @see java.util.Map#clear()
 685  
          */
 686  
         public void clear()
 687  
         {
 688  
             // nothing here
 689  0
         }
 690  
 
 691  
         /**
 692  
          * {@inheritDoc}
 693  
          *
 694  
          * @see java.util.Map#containsKey(java.lang.Object)
 695  
          */
 696  
         public boolean containsKey( Object key )
 697  
         {
 698  0
             if ( !( key instanceof String ) )
 699  
             {
 700  0
                 return false;
 701  
             }
 702  
 
 703  0
             String expression = (String) key;
 704  0
             if ( expression.startsWith( "project." ) || expression.startsWith( "pom." ) )
 705  
             {
 706  
                 try
 707  
                 {
 708  0
                     Object evaluated = ReflectionValueExtractor.evaluate( expression, this.mavenProject );
 709  0
                     if ( evaluated != null )
 710  
                     {
 711  0
                         return true;
 712  
                     }
 713  
                 }
 714  0
                 catch ( Exception e )
 715  
                 {
 716  
                     // uhm do we have to throw a RuntimeException here ?
 717  0
                 }
 718  
             }
 719  
 
 720  0
             return properties.containsKey( key ) || mavenProject.getProperties().containsKey( key );
 721  
         }
 722  
 
 723  
         /**
 724  
          * {@inheritDoc}
 725  
          *
 726  
          * @see java.util.Map#containsValue(java.lang.Object)
 727  
          */
 728  
         public boolean containsValue( Object value )
 729  
         {
 730  0
             throw new UnsupportedOperationException();
 731  
         }
 732  
 
 733  
         /**
 734  
          * {@inheritDoc}
 735  
          *
 736  
          * @see java.util.Map#entrySet()
 737  
          */
 738  
         public Set<Entry<String, Object>> entrySet()
 739  
         {
 740  0
             throw new UnsupportedOperationException();
 741  
         }
 742  
 
 743  
         /**
 744  
          * {@inheritDoc}
 745  
          *
 746  
          * @see java.util.Map#get(java.lang.Object)
 747  
          */
 748  
         public Object get( Object key )
 749  
         {
 750  0
             if ( !( key instanceof String ) )
 751  
             {
 752  0
                 return null;
 753  
             }
 754  
 
 755  0
             String expression = (String) key;
 756  0
             if ( expression.startsWith( "project." ) || expression.startsWith( "pom." ) )
 757  
             {
 758  
                 try
 759  
                 {
 760  0
                     Object evaluated = ReflectionValueExtractor.evaluate( expression, this.mavenProject );
 761  0
                     if ( evaluated != null )
 762  
                     {
 763  0
                         return evaluated;
 764  
                     }
 765  
                 }
 766  0
                 catch ( Exception e )
 767  
                 {
 768  
                     // uhm do we have to throw a RuntimeException here ?
 769  0
                 }
 770  
             }
 771  
 
 772  0
             Object value = properties.get( key );
 773  
 
 774  0
             return ( value != null ? value : this.mavenProject.getProperties().get( key ) );
 775  
 
 776  
         }
 777  
 
 778  
         /**
 779  
          * {@inheritDoc}
 780  
          *
 781  
          * @see java.util.Map#isEmpty()
 782  
          */
 783  
         public boolean isEmpty()
 784  
         {
 785  0
             return this.mavenProject == null && this.mavenProject.getProperties().isEmpty()
 786  
                 && this.properties.isEmpty();
 787  
         }
 788  
 
 789  
         /**
 790  
          * {@inheritDoc}
 791  
          *
 792  
          * @see java.util.Map#keySet()
 793  
          */
 794  
         public Set<String> keySet()
 795  
         {
 796  0
             throw new UnsupportedOperationException();
 797  
         }
 798  
 
 799  
         /**
 800  
          * {@inheritDoc}
 801  
          *
 802  
          * @see java.util.Map#put(java.lang.Object, java.lang.Object)
 803  
          */
 804  
         public Object put( String key, Object value )
 805  
         {
 806  0
             throw new UnsupportedOperationException();
 807  
         }
 808  
 
 809  
         /**
 810  
          * {@inheritDoc}
 811  
          *
 812  
          * @see java.util.Map#putAll(java.util.Map)
 813  
          */
 814  
         public void putAll( Map<? extends String, ? extends Object> t )
 815  
         {
 816  0
             throw new UnsupportedOperationException();
 817  
         }
 818  
 
 819  
         /**
 820  
          * {@inheritDoc}
 821  
          *
 822  
          * @see java.util.Map#remove(java.lang.Object)
 823  
          */
 824  
         public Object remove( Object key )
 825  
         {
 826  0
             throw new UnsupportedOperationException();
 827  
         }
 828  
 
 829  
         /**
 830  
          * {@inheritDoc}
 831  
          *
 832  
          * @see java.util.Map#size()
 833  
          */
 834  
         public int size()
 835  
         {
 836  0
             throw new UnsupportedOperationException();
 837  
         }
 838  
 
 839  
         /**
 840  
          * {@inheritDoc}
 841  
          *
 842  
          * @see java.util.Map#values()
 843  
          */
 844  
         public Collection<Object> values()
 845  
         {
 846  0
             throw new UnsupportedOperationException();
 847  
         }
 848  
     }
 849  
 
 850  
 
 851  
     /**
 852  
      * Converts the specified filesystem path to a URL. The resulting URL has no trailing slash regardless whether the
 853  
      * path denotes a file or a directory.
 854  
      *
 855  
      * @param filename The filesystem path to convert, must not be <code>null</code>.
 856  
      * @return The <code>file:</code> URL for the specified path, never <code>null</code>.
 857  
      */
 858  
     private static String toUrl( String filename )
 859  
     {
 860  
         /*
 861  
          * NOTE: Maven fails to properly handle percent-encoded "file:" URLs (WAGON-111) so don't use File.toURI() here
 862  
          * as-is but use the decoded path component in the URL.
 863  
          */
 864  0
         String url = "file://" + new File( filename ).toURI().getPath();
 865  0
         if ( url.endsWith( "/" ) )
 866  
         {
 867  0
             url = url.substring( 0, url.length() - 1 );
 868  
         }
 869  0
         return url;
 870  
     }
 871  
 }