View Javadoc
1   package org.apache.maven.plugins.invoker;
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.File;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.Collection;
26  import java.util.HashMap;
27  import java.util.Map;
28  import java.util.Properties;
29  import java.util.regex.Matcher;
30  import java.util.regex.Pattern;
31  
32  import org.apache.maven.shared.invoker.InvocationRequest;
33  import org.apache.maven.shared.invoker.InvocationRequest.ReactorFailureBehavior;
34  import org.codehaus.plexus.util.StringUtils;
35  
36  /**
37   * Provides a convenient facade around the <code>invoker.properties</code>.
38   *
39   * @author Benjamin Bentmann
40   */
41  class InvokerProperties
42  {
43      private static final String SELECTOR_PREFIX = "selector.";
44  
45      private static final Pattern ENVIRONMENT_VARIABLES_PATTERN =
46              Pattern.compile( "invoker\\.environmentVariables\\.([A-Za-z][^.]+)(\\.([0-9]+))?" );
47  
48      private enum InvocationProperty
49      {
50          PROJECT( "invoker.project" ),
51          GOALS( "invoker.goals" ),
52          PROFILES( "invoker.profiles" ),
53          MAVEN_OPTS( "invoker.mavenOpts" ),
54          FAILURE_BEHAVIOR( "invoker.failureBehavior" ),
55          NON_RECURSIVE( "invoker.nonRecursive" ),
56          OFFLINE( "invoker.offline" ),
57          SYSTEM_PROPERTIES_FILE( "invoker.systemPropertiesFile" ),
58          DEBUG( "invoker.debug" ),
59          SETTINGS_FILE( "invoker.settingsFile" ),
60          TIMEOUT_IN_SECONDS( "invoker.timeoutInSeconds" ),
61          ORDINAL( "invoker.ordinal" );
62  
63          private final String key;
64  
65          InvocationProperty( final String s )
66          {
67              this.key = s;
68          }
69  
70          @Override
71          public String toString()
72          {
73              return key;
74          }
75      }
76  
77      private enum SelectorProperty
78      {
79          JAVA_VERSION( ".java.version" ),
80          MAVEN_VERSION( ".maven.version" ),
81          OS_FAMLY( ".os.family" );
82  
83          private final String suffix;
84  
85          SelectorProperty( String suffix )
86          {
87              this.suffix = suffix;
88          }
89  
90          @Override
91          public String toString()
92          {
93              return suffix;
94          }
95      }
96  
97      /**
98       * The invoker properties being wrapped.
99       */
100     private final Properties properties;
101 
102     /**
103      * Creates a new facade for the specified invoker properties. The properties will not be copied, so any changes to
104      * them will be reflected by the facade.
105      *
106      * @param properties The invoker properties to wrap, may be <code>null</code> if none.
107      */
108     InvokerProperties( Properties properties )
109     {
110         this.properties = ( properties != null ) ? properties : new Properties();
111     }
112 
113     /**
114      * Gets the invoker properties being wrapped.
115      *
116      * @return The invoker properties being wrapped, never <code>null</code>.
117      */
118     public Properties getProperties()
119     {
120         return this.properties;
121     }
122 
123     /**
124      * Gets the name of the corresponding build job.
125      *
126      * @return The name of the build job or an empty string if not set.
127      */
128     public String getJobName()
129     {
130         return this.properties.getProperty( "invoker.name", "" );
131     }
132 
133     /**
134      * Gets the description of the corresponding build job.
135      *
136      * @return The description of the build job or an empty string if not set.
137      */
138     public String getJobDescription()
139     {
140         return this.properties.getProperty( "invoker.description", "" );
141     }
142 
143     /**
144      * Get the corresponding ordinal value
145      *
146      * @return The ordinal value
147      */
148     public int getOrdinal()
149     {
150         return Integer.parseInt( this.properties.getProperty( "invoker.ordinal", "0" ) );
151     }
152 
153     /**
154      * Gets the specification of JRE versions on which this build job should be run.
155      *
156      * @return The specification of JRE versions or an empty string if not set.
157      */
158     public String getJreVersion()
159     {
160         return this.properties.getProperty( "invoker.java.version", "" );
161     }
162 
163     /**
164      * Gets the specification of JRE versions on which this build job should be run.
165      *
166      * @return The specification of JRE versions or an empty string if not set.
167      */
168     public String getJreVersion( int index )
169     {
170         return this.properties.getProperty( SELECTOR_PREFIX + index + SelectorProperty.JAVA_VERSION,
171                 getJreVersion() );
172     }
173 
174     /**
175      * Gets the specification of Maven versions on which this build job should be run.
176      *
177      * @return The specification of Maven versions on which this build job should be run.
178      * @since 1.5
179      */
180     public String getMavenVersion()
181     {
182         return this.properties.getProperty( "invoker.maven.version", "" );
183     }
184 
185     /**
186      * @param index the selector index
187      * @return The specification of Maven versions on which this build job should be run.
188      * @since 3.0.0
189      */
190     public String getMavenVersion( int index )
191     {
192         return this.properties.getProperty( SELECTOR_PREFIX + index + SelectorProperty.MAVEN_VERSION,
193                 getMavenVersion() );
194     }
195 
196     /**
197      * Gets the specification of OS families on which this build job should be run.
198      *
199      * @return The specification of OS families or an empty string if not set.
200      */
201     public String getOsFamily()
202     {
203         return this.properties.getProperty( "invoker.os.family", "" );
204     }
205 
206     /**
207      * Gets the specification of OS families on which this build job should be run.
208      *
209      * @param index the selector index
210      * @return The specification of OS families or an empty string if not set.
211      * @since 3.0.0
212      */
213     public String getOsFamily( int index )
214     {
215         return this.properties.getProperty( SELECTOR_PREFIX + index + SelectorProperty.OS_FAMLY, getOsFamily() );
216     }
217 
218     public Collection<InvokerToolchain> getToolchains()
219     {
220         return getToolchains( Pattern.compile( "invoker\\.toolchain\\.([^.]+)\\.(.+)" ) );
221     }
222 
223     public Collection<InvokerToolchain> getToolchains( int index )
224     {
225         return getToolchains( Pattern.compile( "selector\\." + index + "\\.invoker\\.toolchain\\.([^.]+)\\.(.+)" ) );
226     }
227 
228     private Collection<InvokerToolchain> getToolchains( Pattern p )
229     {
230         Map<String, InvokerToolchain> toolchains = new HashMap<>();
231         for ( Map.Entry<Object, Object> entry : this.properties.entrySet() )
232         {
233             Matcher m = p.matcher( entry.getKey().toString() );
234             if ( m.matches() )
235             {
236                 String type = m.group( 1 );
237                 String providesKey = m.group( 2 );
238                 String providesValue = entry.getValue().toString();
239 
240                 InvokerToolchain tc = toolchains.get( type );
241                 if ( tc == null )
242                 {
243                     tc = new InvokerToolchain( type );
244                     toolchains.put( type, tc );
245                 }
246                 tc.addProvides( providesKey, providesValue );
247             }
248         }
249         return toolchains.values();
250     }
251 
252     /**
253      * Extract environment variable from properties for given index.
254      * Every environment variable without index is also returned.
255      *
256      * @param index index to lookup
257      * @return map of environment name and value
258      */
259 
260     private Map<String, String> getEnvironmentVariables( int index )
261     {
262 
263         Map<String, String> envItems = new HashMap<>();
264 
265         for ( Map.Entry<Object, Object> entry : properties.entrySet() )
266         {
267             Matcher matcher = ENVIRONMENT_VARIABLES_PATTERN.matcher( entry.getKey().toString() );
268             if ( matcher.matches() )
269             {
270 
271                 if ( String.valueOf( index ).equals( matcher.group( 3 ) ) )
272                 {
273                     // variables with index has higher priority, so override
274                     envItems.put( matcher.group( 1 ), entry.getValue().toString() );
275                 }
276                 else if ( matcher.group( 3 ) == null )
277                 {
278                     // variables without index has lower priority, so check if exist
279                     if ( !envItems.containsKey( matcher.group( 1 ) ) )
280                     {
281                         envItems.put( matcher.group( 1 ), entry.getValue().toString() );
282                     }
283                 }
284             }
285         }
286         return envItems;
287     }
288 
289     /**
290      * Determines whether these invoker properties contain a build definition for the specified invocation index.
291      *
292      * @param index The one-based index of the invocation to check for, must not be negative.
293      * @return <code>true</code> if the invocation with the specified index is defined, <code>false</code> otherwise.
294      */
295     public boolean isInvocationDefined( int index )
296     {
297         for ( InvocationProperty prop : InvocationProperty.values() )
298         {
299             if ( properties.getProperty( prop.toString() + '.' + index ) != null )
300             {
301                 return true;
302             }
303         }
304         return false;
305     }
306 
307     /**
308      * Determines whether these invoker properties contain a build definition for the specified selector index.
309      *
310      * @param index the index
311      * @return <code>true</code> if the selector with the specified index is defined, <code>false</code> otherwise.
312      * @since 3.0.0
313      */
314     public boolean isSelectorDefined( int index )
315     {
316         for ( SelectorProperty prop : SelectorProperty.values() )
317         {
318             if ( properties.getProperty( SELECTOR_PREFIX + index + prop.suffix ) != null )
319             {
320                 return true;
321             }
322         }
323         return false;
324     }
325 
326     /**
327      * Configures the specified invocation request from these invoker properties. Settings not present in the invoker
328      * properties will be left unchanged in the invocation request.
329      *
330      * @param request The invocation request to configure, must not be <code>null</code>.
331      * @param index The one-based index of the invocation to configure, must not be negative.
332      */
333     public void configureInvocation( InvocationRequest request, int index )
334     {
335         String project = get( InvocationProperty.PROJECT, index );
336         if ( project != null )
337         {
338             File file = new File( request.getBaseDirectory(), project );
339             if ( file.isFile() )
340             {
341                 request.setBaseDirectory( file.getParentFile() );
342                 request.setPomFile( file );
343             }
344             else
345             {
346                 request.setBaseDirectory( file );
347                 request.setPomFile( null );
348             }
349         }
350 
351         String goals = get( InvocationProperty.GOALS, index );
352         if ( goals != null )
353         {
354             request.setGoals( new ArrayList<>( Arrays.asList( StringUtils.split( goals, ", \t\n\r\f" ) ) ) );
355         }
356 
357         String profiles = get( InvocationProperty.PROFILES, index );
358         if ( profiles != null )
359         {
360             // CHECKSTYLE_OFF: LineLength
361             request.setProfiles( new ArrayList<>( Arrays.asList( StringUtils.split( profiles,
362                                                                                           ", \t\n\r\f" ) ) ) );
363             // CHECKSTYLE_ON: LineLength
364         }
365 
366         String mvnOpts = get( InvocationProperty.MAVEN_OPTS, index );
367         if ( mvnOpts != null )
368         {
369             request.setMavenOpts( mvnOpts );
370         }
371 
372         String failureBehavior = get( InvocationProperty.FAILURE_BEHAVIOR, index );
373         if ( failureBehavior != null )
374         {
375             ReactorFailureBehavior valueOf =
376                 InvocationRequest.ReactorFailureBehavior.valueOfByLongOption( failureBehavior );
377             request.setReactorFailureBehavior( valueOf );
378         }
379 
380         String nonRecursive = get( InvocationProperty.NON_RECURSIVE, index );
381         if ( nonRecursive != null )
382         {
383             request.setRecursive( !Boolean.parseBoolean( nonRecursive ) );
384         }
385 
386         String offline = get( InvocationProperty.OFFLINE, index );
387         if ( offline != null )
388         {
389             request.setOffline( Boolean.parseBoolean( offline ) );
390         }
391 
392         String debug = get( InvocationProperty.DEBUG, index );
393         if ( debug != null )
394         {
395             request.setDebug( Boolean.parseBoolean( debug ) );
396         }
397 
398         for ( Map.Entry<String, String> envItem : getEnvironmentVariables( index ).entrySet() )
399         {
400             request.addShellEnvironment( envItem.getKey(), envItem.getValue() );
401         }
402     }
403 
404     /**
405      * Checks whether the specified exit code matches the one expected for the given invocation.
406      *
407      * @param exitCode The exit code of the Maven invocation to check.
408      * @param index The index of the invocation for which to check the exit code, must not be negative.
409      * @return <code>true</code> if the exit code is zero and a success was expected or if the exit code is non-zero and
410      *         a failue was expected, <code>false</code> otherwise.
411      */
412     public boolean isExpectedResult( int exitCode, int index )
413     {
414         boolean nonZeroExit = "failure".equalsIgnoreCase( get( "invoker.buildResult", index ) );
415         return ( exitCode != 0 ) == nonZeroExit;
416     }
417 
418     /**
419      * Gets the path to the properties file used to set the system properties for the specified invocation.
420      *
421      * @param index The index of the invocation, must not be negative.
422      * @return The path to the properties file or <code>null</code> if not set.
423      */
424     public String getSystemPropertiesFile( int index )
425     {
426         return get( InvocationProperty.SYSTEM_PROPERTIES_FILE, index );
427     }
428 
429     /**
430      * Gets the settings file used for the specified invocation.
431      * 
432      * @param index The index of the invocation, must not be negative.
433      * @return the value for the settings file or <code>null</code> if not set.
434      */
435     public String getSettingsFile( int index )
436     {
437         return get( InvocationProperty.SETTINGS_FILE, index );
438     }
439 
440     /**
441      * Get timeout to execute the project
442      * @param index index The index of the invocation, must not be negative.
443      * @return the value for the timeout or -1
444      */
445     public int getTimeoutInSeconds( int index )
446     {
447         String timeoutInSecondsStr = get( InvocationProperty.TIMEOUT_IN_SECONDS, index );
448         if ( StringUtils.isEmpty( timeoutInSecondsStr ) )
449         {
450             return -1;
451         }
452         // catch NumberFormatException? well we assume users knows what they do :-)
453         return Integer.parseInt( timeoutInSecondsStr );
454     }
455     /**
456      * Gets a value from the invoker properties. The invoker properties are intended to describe the invocation settings
457      * for multiple builds of the same project. For this reason, the properties are indexed. First, a property named
458      * <code>key.index</code> will be queried. If this property does not exist, the value of the property named
459      * <code>key</code> will finally be returned.
460      *
461      * @param key The (base) key for the invoker property to lookup, must not be <code>null</code>.
462      * @param index The index of the invocation for which to retrieve the value, must not be negative.
463      * @return The value for the requested invoker property or <code>null</code> if not defined.
464      */
465     String get( String key, int index )
466     {
467         if ( index < 0 )
468         {
469             throw new IllegalArgumentException( "invalid invocation index: " + index );
470         }
471 
472         String value = properties.getProperty( key + '.' + index );
473         if ( value == null )
474         {
475             value = properties.getProperty( key );
476         }
477         return value;
478     }
479 
480     private String get( InvocationProperty prop, int index )
481     {
482         return get( prop.toString(), index );
483     }
484 }