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 }