View Javadoc

1   package org.apache.maven.plugin.testing;
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 java.io.BufferedReader;
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.InputStream;
26  import java.io.Reader;
27  import java.lang.reflect.AccessibleObject;
28  import java.lang.reflect.Field;
29  import java.util.Arrays;
30  import java.util.HashMap;
31  import java.util.Map;
32  
33  import org.apache.maven.artifact.Artifact;
34  import org.apache.maven.execution.DefaultMavenExecutionRequest;
35  import org.apache.maven.execution.DefaultMavenExecutionResult;
36  import org.apache.maven.execution.MavenExecutionRequest;
37  import org.apache.maven.execution.MavenExecutionResult;
38  import org.apache.maven.execution.MavenSession;
39  import org.apache.maven.lifecycle.internal.MojoDescriptorCreator;
40  import org.apache.maven.model.Plugin;
41  import org.apache.maven.monitor.logging.DefaultLog;
42  import org.apache.maven.plugin.Mojo;
43  import org.apache.maven.plugin.MojoExecution;
44  import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
45  import org.apache.maven.plugin.descriptor.MojoDescriptor;
46  import org.apache.maven.plugin.descriptor.Parameter;
47  import org.apache.maven.plugin.descriptor.PluginDescriptor;
48  import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
49  import org.apache.maven.plugin.logging.Log;
50  import org.apache.maven.project.MavenProject;
51  import org.apache.maven.repository.RepositorySystem;
52  import org.apache.maven.repository.internal.MavenRepositorySystemSession;
53  import org.codehaus.plexus.ContainerConfiguration;
54  import org.codehaus.plexus.DefaultContainerConfiguration;
55  import org.codehaus.plexus.DefaultPlexusContainer;
56  import org.codehaus.plexus.PlexusContainer;
57  import org.codehaus.plexus.PlexusContainerException;
58  import org.codehaus.plexus.PlexusTestCase;
59  import org.codehaus.plexus.classworlds.ClassWorld;
60  import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
61  import org.codehaus.plexus.component.configurator.ComponentConfigurator;
62  import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
63  import org.codehaus.plexus.component.repository.ComponentDescriptor;
64  import org.codehaus.plexus.configuration.PlexusConfiguration;
65  import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
66  import org.codehaus.plexus.logging.LoggerManager;
67  import org.codehaus.plexus.util.InterpolationFilterReader;
68  import org.codehaus.plexus.util.ReaderFactory;
69  import org.codehaus.plexus.util.ReflectionUtils;
70  import org.codehaus.plexus.util.StringUtils;
71  import org.codehaus.plexus.util.xml.XmlStreamReader;
72  import org.codehaus.plexus.util.xml.Xpp3Dom;
73  import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
74  
75  /**
76   * TODO: add a way to use the plugin POM for the lookup so that the user doesn't have to provide the a:g:v:goal
77   * as the role hint for the mojo lookup.
78   * TODO: standardize the execution of the mojo and looking at the results, but could simply have a template method
79   * for verifying the state of the mojo post execution
80   * TODO: need a way to look at the state of the mojo without adding getters, this could be where we finally specify
81   * the expressions which extract values from the mojo.
82   * TODO: create a standard directory structure for picking up POMs to make this even easier, we really just need a testing
83   * descriptor and make this entirely declarative!
84   *
85   * @author jesse
86   * @version $Id: AbstractMojoTestCase.java 1340746 2012-05-20 14:52:12Z hboutemy $
87   */
88  public abstract class AbstractMojoTestCase
89      extends PlexusTestCase
90  {
91      private ComponentConfigurator configurator;
92  
93      private PlexusContainer container;
94  
95      private Map<String, MojoDescriptor> mojoDescriptors;
96      
97      /*
98       * for the harness I think we have decided against going the route of using the maven project builder.
99       * instead I think we are going to try and make an instance of the localrespository and assign that
100      * to either the project stub or into the mojo directly with injection...not sure yet though.
101      */
102     //private MavenProjectBuilder projectBuilder;
103 
104     protected void setUp()
105         throws Exception
106     {
107         configurator = getContainer().lookup( ComponentConfigurator.class, "basic" );
108 
109         InputStream is = getClass().getResourceAsStream( "/" + getPluginDescriptorLocation() );
110 
111         XmlStreamReader reader = ReaderFactory.newXmlReader( is );
112 
113         InterpolationFilterReader interpolationFilterReader =
114             new InterpolationFilterReader( new BufferedReader( reader ), container.getContext().getContextData() );
115 
116         PluginDescriptor pluginDescriptor = new PluginDescriptorBuilder().build( interpolationFilterReader );
117 
118         Artifact artifact =
119             lookup( RepositorySystem.class ).createArtifact( pluginDescriptor.getGroupId(),
120                                                              pluginDescriptor.getArtifactId(),
121                                                              pluginDescriptor.getVersion(), ".jar" );
122         artifact.setFile( new File( getBasedir() ).getCanonicalFile() );
123         pluginDescriptor.setPluginArtifact( artifact );
124         pluginDescriptor.setArtifacts( Arrays.asList( artifact ) );
125 
126         for ( ComponentDescriptor<?> desc : pluginDescriptor.getComponents() )
127         {
128             getContainer().addComponentDescriptor( desc );
129         }
130 
131         mojoDescriptors = new HashMap<String, MojoDescriptor>();
132         for ( MojoDescriptor mojoDescriptor : pluginDescriptor.getMojos() )
133         {
134             mojoDescriptors.put( mojoDescriptor.getGoal(), mojoDescriptor );
135         }
136     }
137 
138     protected InputStream getPublicDescriptorStream()
139         throws Exception
140     {
141         return new FileInputStream( new File( getPluginDescriptorPath() ) );
142     }
143 
144     protected String getPluginDescriptorPath()
145     {
146         return getBasedir() + "/target/classes/META-INF/maven/plugin.xml";
147     }
148 
149     protected String getPluginDescriptorLocation()
150     {
151         return "META-INF/maven/plugin.xml";
152     }
153 
154     protected void setupContainer()
155     {
156         ContainerConfiguration cc = setupContainerConfiguration();
157         try
158         {
159             container = new DefaultPlexusContainer( cc );
160         }
161         catch ( PlexusContainerException e )
162         {
163             e.printStackTrace();
164             fail( "Failed to create plexus container." );
165         }   
166     }
167 
168     protected ContainerConfiguration setupContainerConfiguration()
169     {
170         ClassWorld classWorld = new ClassWorld( "plexus.core", Thread.currentThread().getContextClassLoader() );
171 
172         return new DefaultContainerConfiguration().setClassWorld( classWorld ).setName( "embedder" );
173     }
174     
175     protected PlexusContainer getContainer()
176     {
177         if ( container == null )
178         {
179             setupContainer();
180         }
181 
182         return container;
183     }    
184     
185     /**
186      * Lookup the mojo leveraging the subproject pom
187      *
188      * @param goal
189      * @param pluginPom
190      * @return a Mojo instance
191      * @throws Exception
192      */
193     protected Mojo lookupMojo( String goal, String pluginPom )
194         throws Exception
195     {
196         return lookupMojo( goal, new File( pluginPom ) );
197     }
198 
199     /**
200      * Lookup an empty mojo
201      *
202      * @param goal
203      * @param pluginPom
204      * @return a Mojo instance
205      * @throws Exception
206      */
207     protected Mojo lookupEmptyMojo( String goal, String pluginPom )
208         throws Exception
209     {
210         return lookupEmptyMojo( goal, new File( pluginPom ) );
211     }
212 
213     /**
214      * Lookup the mojo leveraging the actual subprojects pom
215      *
216      * @param goal
217      * @param pom
218      * @return a Mojo instance
219      * @throws Exception
220      */
221     protected Mojo lookupMojo( String goal, File pom )
222         throws Exception
223     {
224         File pluginPom = new File( getBasedir(), "pom.xml" );
225 
226         Xpp3Dom pluginPomDom = Xpp3DomBuilder.build( ReaderFactory.newXmlReader( pluginPom ) );
227 
228         String artifactId = pluginPomDom.getChild( "artifactId" ).getValue();
229 
230         String groupId = resolveFromRootThenParent( pluginPomDom, "groupId" );
231 
232         String version = resolveFromRootThenParent( pluginPomDom, "version" );
233 
234         PlexusConfiguration pluginConfiguration = extractPluginConfiguration( artifactId, pom );
235 
236         return lookupMojo( groupId, artifactId, version, goal, pluginConfiguration );
237     }
238 
239     /**
240      * Lookup the mojo leveraging the actual subprojects pom
241      *
242      * @param goal
243      * @param pom
244      * @return a Mojo instance
245      * @throws Exception
246      */
247     protected Mojo lookupEmptyMojo( String goal, File pom )
248         throws Exception
249     {
250         File pluginPom = new File( getBasedir(), "pom.xml" );
251 
252         Xpp3Dom pluginPomDom = Xpp3DomBuilder.build( ReaderFactory.newXmlReader( pluginPom ) );
253 
254         String artifactId = pluginPomDom.getChild( "artifactId" ).getValue();
255 
256         String groupId = resolveFromRootThenParent( pluginPomDom, "groupId" );
257 
258         String version = resolveFromRootThenParent( pluginPomDom, "version" );
259 
260         return lookupMojo( groupId, artifactId, version, goal, null );
261     }
262 
263     /*
264      protected Mojo lookupMojo( String groupId, String artifactId, String version, String goal, File pom )
265      throws Exception
266      {
267      PlexusConfiguration pluginConfiguration = extractPluginConfiguration( artifactId, pom );
268 
269      return lookupMojo( groupId, artifactId, version, goal, pluginConfiguration );
270      }
271      */
272     /**
273      * lookup the mojo while we have all of the relavent information
274      *
275      * @param groupId
276      * @param artifactId
277      * @param version
278      * @param goal
279      * @param pluginConfiguration
280      * @return a Mojo instance
281      * @throws Exception
282      */
283     protected Mojo lookupMojo( String groupId, String artifactId, String version, String goal,
284                                PlexusConfiguration pluginConfiguration )
285         throws Exception
286     {
287         validateContainerStatus();
288 
289         // pluginkey = groupId : artifactId : version : goal
290 
291         Mojo mojo = (Mojo) lookup( Mojo.ROLE, groupId + ":" + artifactId + ":" + version + ":" + goal );
292 
293         LoggerManager loggerManager = (LoggerManager) getContainer().lookup( LoggerManager.class );
294         
295         Log mojoLogger = new DefaultLog( loggerManager.getLoggerForComponent( Mojo.ROLE ) );
296 
297         mojo.setLog( mojoLogger );
298 
299         if ( pluginConfiguration != null )
300         {
301             /* requires v10 of plexus container for lookup on expression evaluator
302              ExpressionEvaluator evaluator = (ExpressionEvaluator) getContainer().lookup( ExpressionEvaluator.ROLE,
303                                                                                          "stub-evaluator" );
304              */
305             ExpressionEvaluator evaluator = new ResolverExpressionEvaluatorStub();
306 
307             configurator.configureComponent( mojo, pluginConfiguration, evaluator, getContainer().getContainerRealm() );
308         }
309 
310         return mojo;
311     }
312 
313     protected Mojo lookupConfiguredMojo( MavenProject project, String goal )
314         throws Exception
315     {
316         return lookupConfiguredMojo( newMavenSession( project ), newMojoExecution( goal ) );
317     }
318 
319     protected Mojo lookupConfiguredMojo( MavenSession session, MojoExecution execution )
320         throws Exception, ComponentConfigurationException
321     {
322         MavenProject project = session.getCurrentProject();
323         MojoDescriptor mojoDescriptor = execution.getMojoDescriptor();
324 
325         Mojo mojo = (Mojo) lookup( mojoDescriptor.getRole(), mojoDescriptor.getRoleHint() );
326 
327         ExpressionEvaluator evaluator = new PluginParameterExpressionEvaluator( session, execution );
328 
329         Xpp3Dom configuration = null;
330         Plugin plugin = project.getPlugin( mojoDescriptor.getPluginDescriptor().getPluginLookupKey() );
331         if ( plugin != null )
332         {
333             configuration = (Xpp3Dom) plugin.getConfiguration();
334         }
335         if ( configuration == null )
336         {
337             configuration = new Xpp3Dom( "configuration" );
338         }
339         configuration = Xpp3Dom.mergeXpp3Dom( execution.getConfiguration(), configuration );
340 
341         PlexusConfiguration pluginConfiguration = new XmlPlexusConfiguration( configuration );
342 
343         configurator.configureComponent( mojo, pluginConfiguration, evaluator, getContainer().getContainerRealm() );
344 
345         return mojo;
346     }
347 
348     protected MavenSession newMavenSession( MavenProject project )
349     {
350         MavenExecutionRequest request = new DefaultMavenExecutionRequest();
351         MavenExecutionResult result = new DefaultMavenExecutionResult();
352 
353         MavenSession session = new MavenSession( container, new MavenRepositorySystemSession(), request, result );
354         session.setCurrentProject( project );
355         session.setProjects( Arrays.asList( project ) );
356         return session;
357     }
358 
359     protected MojoExecution newMojoExecution( String goal )
360     {
361         MojoDescriptor mojoDescriptor = mojoDescriptors.get( goal );
362         assertNotNull( mojoDescriptor );
363         MojoExecution execution = new MojoExecution( mojoDescriptor );
364         finalizeMojoConfiguration( execution );
365         return execution;
366     }
367 
368     // copy&paste from org.apache.maven.lifecycle.internal.DefaultLifecycleExecutionPlanCalculator.finalizeMojoConfiguration(MojoExecution)
369     private void finalizeMojoConfiguration( MojoExecution mojoExecution )
370     {
371         MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
372 
373         Xpp3Dom executionConfiguration = mojoExecution.getConfiguration();
374         if ( executionConfiguration == null )
375         {
376             executionConfiguration = new Xpp3Dom( "configuration" );
377         }
378 
379         Xpp3Dom defaultConfiguration = MojoDescriptorCreator.convert( mojoDescriptor );;
380 
381         Xpp3Dom finalConfiguration = new Xpp3Dom( "configuration" );
382 
383         if ( mojoDescriptor.getParameters() != null )
384         {
385             for ( Parameter parameter : mojoDescriptor.getParameters() )
386             {
387                 Xpp3Dom parameterConfiguration = executionConfiguration.getChild( parameter.getName() );
388 
389                 if ( parameterConfiguration == null )
390                 {
391                     parameterConfiguration = executionConfiguration.getChild( parameter.getAlias() );
392                 }
393 
394                 Xpp3Dom parameterDefaults = defaultConfiguration.getChild( parameter.getName() );
395 
396                 parameterConfiguration = Xpp3Dom.mergeXpp3Dom( parameterConfiguration, parameterDefaults, Boolean.TRUE );
397 
398                 if ( parameterConfiguration != null )
399                 {
400                     parameterConfiguration = new Xpp3Dom( parameterConfiguration, parameter.getName() );
401 
402                     if ( StringUtils.isEmpty( parameterConfiguration.getAttribute( "implementation" ) )
403                         && StringUtils.isNotEmpty( parameter.getImplementation() ) )
404                     {
405                         parameterConfiguration.setAttribute( "implementation", parameter.getImplementation() );
406                     }
407 
408                     finalConfiguration.addChild( parameterConfiguration );
409                 }
410             }
411         }
412 
413         mojoExecution.setConfiguration( finalConfiguration );
414     }
415 
416     /**
417      * @param artifactId
418      * @param pom
419      * @return the plexus configuration
420      * @throws Exception
421      */
422     protected PlexusConfiguration extractPluginConfiguration( String artifactId, File pom )
423         throws Exception
424     {
425         Reader reader = ReaderFactory.newXmlReader( pom );
426 
427         Xpp3Dom pomDom = Xpp3DomBuilder.build( reader );
428 
429         return extractPluginConfiguration( artifactId, pomDom );
430     }
431 
432     /**
433      * @param artifactId
434      * @param pomDom
435      * @return the plexus configuration
436      * @throws Exception
437      */
438     protected PlexusConfiguration extractPluginConfiguration( String artifactId, Xpp3Dom pomDom )
439         throws Exception
440     {
441         Xpp3Dom pluginConfigurationElement = null;
442 
443         Xpp3Dom buildElement = pomDom.getChild( "build" );
444         if ( buildElement != null )
445         {
446             Xpp3Dom pluginsRootElement = buildElement.getChild( "plugins" );
447 
448             if ( pluginsRootElement != null )
449             {
450                 Xpp3Dom[] pluginElements = pluginsRootElement.getChildren();
451 
452                 for ( Xpp3Dom pluginElement : pluginElements )
453                 {
454                     String pluginElementArtifactId = pluginElement.getChild( "artifactId" ).getValue();
455 
456                     if ( pluginElementArtifactId.equals( artifactId ) )
457                     {
458                         pluginConfigurationElement = pluginElement.getChild( "configuration" );
459 
460                         break;
461                     }
462                 }
463 
464                 if ( pluginConfigurationElement == null )
465                 {
466                     throw new ConfigurationException( "Cannot find a configuration element for a plugin with an "
467                         + "artifactId of " + artifactId + "." );
468                 }
469             }
470         }
471 
472         if ( pluginConfigurationElement == null )
473         {
474             throw new ConfigurationException( "Cannot find a configuration element for a plugin with an artifactId of "
475                 + artifactId + "." );
476         }
477 
478         return new XmlPlexusConfiguration( pluginConfigurationElement );
479     }
480 
481     /**
482      * Configure the mojo
483      *
484      * @param mojo
485      * @param artifactId
486      * @param pom
487      * @return a Mojo instance
488      * @throws Exception
489      */
490     protected Mojo configureMojo( Mojo mojo, String artifactId, File pom )
491         throws Exception
492     {
493         validateContainerStatus();
494 
495         PlexusConfiguration pluginConfiguration = extractPluginConfiguration( artifactId, pom );
496 
497         ExpressionEvaluator evaluator = new ResolverExpressionEvaluatorStub();
498 
499         configurator.configureComponent( mojo, pluginConfiguration, evaluator, getContainer().getContainerRealm() );
500 
501         return mojo;
502     }
503 
504     /**
505      * Configure the mojo with the given plexus configuration
506      *
507      * @param mojo
508      * @param pluginConfiguration
509      * @return a Mojo instance
510      * @throws Exception
511      */
512     protected Mojo configureMojo( Mojo mojo, PlexusConfiguration pluginConfiguration )
513         throws Exception
514     {
515         validateContainerStatus();
516 
517         ExpressionEvaluator evaluator = new ResolverExpressionEvaluatorStub();
518 
519         configurator.configureComponent( mojo, pluginConfiguration, evaluator, getContainer().getContainerRealm() );
520 
521         return mojo;
522     }
523 
524     /**
525      * Convenience method to obtain the value of a variable on a mojo that might not have a getter.
526      *
527      * NOTE: the caller is responsible for casting to to what the desired type is.
528      *
529      * @param object
530      * @param variable
531      * @return object value of variable
532      * @throws IllegalArgumentException
533      */
534     protected Object getVariableValueFromObject( Object object, String variable )
535         throws IllegalAccessException
536     {
537         Field field = ReflectionUtils.getFieldByNameIncludingSuperclasses( variable, object.getClass() );
538 
539         field.setAccessible( true );
540 
541         return field.get( object );
542     }
543 
544     /**
545      * Convenience method to obtain all variables and values from the mojo (including its superclasses)
546      *
547      * Note: the values in the map are of type Object so the caller is responsible for casting to desired types.
548      *
549      * @param object
550      * @return map of variable names and values
551      */
552     protected Map<String, Object> getVariablesAndValuesFromObject( Object object )
553         throws IllegalAccessException
554     {
555         return getVariablesAndValuesFromObject( object.getClass(), object );
556     }
557 
558     /**
559      * Convenience method to obtain all variables and values from the mojo (including its superclasses)
560      *
561      * Note: the values in the map are of type Object so the caller is responsible for casting to desired types.
562      *
563      * @param clazz
564      * @param object
565      * @return map of variable names and values
566      */
567     protected Map<String, Object> getVariablesAndValuesFromObject( Class<?> clazz, Object object )
568         throws IllegalAccessException
569     {
570         Map<String, Object> map = new HashMap<String, Object>();
571 
572         Field[] fields = clazz.getDeclaredFields();
573 
574         AccessibleObject.setAccessible( fields, true );
575 
576         for ( Field field : fields )
577         {
578             map.put( field.getName(), field.get( object ) );
579         }
580 
581         Class<?> superclass = clazz.getSuperclass();
582 
583         if ( !Object.class.equals( superclass ) )
584         {
585             map.putAll( getVariablesAndValuesFromObject( superclass, object ) );
586         }
587 
588         return map;
589     }
590 
591     /**
592      * Convenience method to set values to variables in objects that don't have setters
593      *
594      * @param object
595      * @param variable
596      * @param value
597      * @throws IllegalAccessException
598      */
599     protected void setVariableValueToObject( Object object, String variable, Object value )
600         throws IllegalAccessException
601     {
602         Field field = ReflectionUtils.getFieldByNameIncludingSuperclasses( variable, object.getClass() );
603 
604         field.setAccessible( true );
605 
606         field.set( object, value );
607     }
608 
609     /**
610      * sometimes the parent element might contain the correct value so generalize that access
611      *
612      * TODO find out where this is probably done elsewhere
613      *
614      * @param pluginPomDom
615      * @param element
616      * @return
617      * @throws Exception
618      */
619     private String resolveFromRootThenParent( Xpp3Dom pluginPomDom, String element )
620         throws Exception
621     {
622         Xpp3Dom elementDom = pluginPomDom.getChild( element );
623 
624         // parent might have the group Id so resolve it
625         if ( elementDom == null )
626         {
627             Xpp3Dom pluginParentDom = pluginPomDom.getChild( "parent" );
628 
629             if ( pluginParentDom != null )
630             {
631                 elementDom = pluginParentDom.getChild( element );
632 
633                 if ( elementDom == null )
634                 {
635                     throw new Exception( "unable to determine " + element );
636                 }
637 
638                 return elementDom.getValue();
639             }
640 
641             throw new Exception( "unable to determine " + element );
642         }
643 
644         return elementDom.getValue();
645     }
646 
647     /**
648      * We should make sure this is called in each method that makes use of the container,
649      * otherwise we throw ugly NPE's
650      *
651      * crops up when the subclassing code defines the setUp method but doesn't call super.setUp()
652      *
653      * @throws Exception
654      */
655     private void validateContainerStatus()
656         throws Exception
657     {
658         if ( getContainer() != null )
659         {
660             return;
661         }
662 
663         throw new Exception( "container is null, make sure super.setUp() is called" );
664     }
665 }