1 package org.apache.maven.model.interpolation;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.model.InputLocation;
23 import org.apache.maven.model.Model;
24 import org.apache.maven.model.building.ModelBuildingRequest;
25 import org.apache.maven.model.building.ModelProblem.Severity;
26 import org.apache.maven.model.building.ModelProblem.Version;
27 import org.apache.maven.model.building.ModelProblemCollector;
28 import org.apache.maven.model.building.ModelProblemCollectorRequest;
29 import org.codehaus.plexus.interpolation.InterpolationException;
30 import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
31 import org.codehaus.plexus.interpolation.RecursionInterceptor;
32 import org.codehaus.plexus.interpolation.StringSearchInterpolator;
33 import org.codehaus.plexus.interpolation.ValueSource;
34
35 import java.io.File;
36 import java.lang.reflect.Array;
37 import java.lang.reflect.Field;
38 import java.lang.reflect.Modifier;
39 import java.security.AccessController;
40 import java.security.PrivilegedAction;
41 import java.util.ArrayList;
42 import java.util.Collection;
43 import java.util.HashMap;
44 import java.util.LinkedList;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.concurrent.ConcurrentHashMap;
48
49 import javax.inject.Named;
50 import javax.inject.Singleton;
51
52
53
54
55 @Named
56 @Singleton
57 public class StringSearchModelInterpolator
58 extends AbstractStringBasedModelInterpolator
59 {
60 private static final Map<Class<?>, InterpolateObjectAction.CacheItem> CACHED_ENTRIES =
61 new ConcurrentHashMap<>( 80, 0.75f, 2 );
62
63
64 private interface InnerInterpolator
65 {
66 String interpolate( String value );
67 }
68
69 @Override
70 public Model interpolateModel( Model model, File projectDir, ModelBuildingRequest config,
71 ModelProblemCollector problems )
72 {
73 interpolateObject( model, model, projectDir, config, problems );
74 return model;
75 }
76
77 void interpolateObject( Object obj, Model model, File projectDir, ModelBuildingRequest config,
78 ModelProblemCollector problems )
79 {
80 List<? extends ValueSource> valueSources = createValueSources( model, projectDir, config, problems );
81 List<? extends InterpolationPostProcessor> postProcessors = createPostProcessors( model, projectDir, config );
82
83 InnerInterpolator innerInterpolator = createInterpolator( valueSources, postProcessors, problems );
84
85 PrivilegedAction<Object> action = new InterpolateObjectAction( obj, innerInterpolator, problems );
86 AccessController.doPrivileged( action );
87 }
88
89 private InnerInterpolator createInterpolator( List<? extends ValueSource> valueSources,
90 List<? extends InterpolationPostProcessor> postProcessors,
91 final ModelProblemCollector problems )
92 {
93 final Map<String, String> cache = new HashMap<>();
94 final StringSearchInterpolator interpolator = new StringSearchInterpolator();
95 interpolator.setCacheAnswers( true );
96 for ( ValueSource vs : valueSources )
97 {
98 interpolator.addValueSource( vs );
99 }
100 for ( InterpolationPostProcessor postProcessor : postProcessors )
101 {
102 interpolator.addPostProcessor( postProcessor );
103 }
104 final RecursionInterceptor recursionInterceptor = createRecursionInterceptor();
105 return new InnerInterpolator()
106 {
107 @Override
108 public String interpolate( String value )
109 {
110 if ( value != null && value.contains( "${" ) )
111 {
112 String c = cache.get( value );
113 if ( c == null )
114 {
115 try
116 {
117 c = interpolator.interpolate( value, recursionInterceptor );
118 }
119 catch ( InterpolationException e )
120 {
121 problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE )
122 .setMessage( e.getMessage() ).setException( e ) );
123 }
124 cache.put( value, c );
125 }
126 return c;
127 }
128 return value;
129 }
130 };
131 }
132
133 private static final class InterpolateObjectAction
134 implements PrivilegedAction<Object>
135 {
136 private final LinkedList<Object> interpolationTargets;
137
138 private final InnerInterpolator interpolator;
139
140 private final ModelProblemCollector problems;
141
142 InterpolateObjectAction( Object target, InnerInterpolator interpolator, ModelProblemCollector problems )
143 {
144 this.interpolationTargets = new LinkedList<>();
145 interpolationTargets.add( target );
146 this.interpolator = interpolator;
147 this.problems = problems;
148 }
149
150 @Override
151 public Object run()
152 {
153 while ( !interpolationTargets.isEmpty() )
154 {
155 Object obj = interpolationTargets.removeFirst();
156
157 traverseObjectWithParents( obj.getClass(), obj );
158 }
159 return null;
160 }
161
162 private String interpolate( String value )
163 {
164 return interpolator.interpolate( value );
165 }
166
167 private void traverseObjectWithParents( Class<?> cls, Object target )
168 {
169 if ( cls == null )
170 {
171 return;
172 }
173
174 CacheItem cacheEntry = getCacheEntry( cls );
175 if ( cacheEntry.isArray() )
176 {
177 evaluateArray( target, this );
178 }
179 else if ( cacheEntry.isQualifiedForInterpolation )
180 {
181 cacheEntry.interpolate( target, this );
182
183 traverseObjectWithParents( cls.getSuperclass(), target );
184 }
185 }
186
187 private CacheItem getCacheEntry( Class<?> cls )
188 {
189 CacheItem cacheItem = CACHED_ENTRIES.get( cls );
190 if ( cacheItem == null )
191 {
192 cacheItem = new CacheItem( cls );
193 CACHED_ENTRIES.put( cls, cacheItem );
194 }
195 return cacheItem;
196 }
197
198 private static void evaluateArray( Object target, InterpolateObjectAction ctx )
199 {
200 int len = Array.getLength( target );
201 for ( int i = 0; i < len; i++ )
202 {
203 Object value = Array.get( target, i );
204 if ( value != null )
205 {
206 if ( String.class == value.getClass() )
207 {
208 String interpolated = ctx.interpolate( (String) value );
209
210 if ( !interpolated.equals( value ) )
211 {
212 Array.set( target, i, interpolated );
213 }
214 }
215 else
216 {
217 ctx.interpolationTargets.add( value );
218 }
219 }
220 }
221 }
222
223 private static class CacheItem
224 {
225 private final boolean isArray;
226
227 private final boolean isQualifiedForInterpolation;
228
229 private final CacheField[] fields;
230
231 private boolean isQualifiedForInterpolation( Class<?> cls )
232 {
233 Package pkg = cls.getPackage();
234 if ( pkg == null )
235 {
236 return true;
237 }
238 String pkgName = pkg.getName();
239 return !pkgName.startsWith( "java." ) && !pkgName.startsWith( "javax." );
240 }
241
242 private boolean isQualifiedForInterpolation( Field field, Class<?> fieldType )
243 {
244 if ( Map.class.equals( fieldType ) && "locations".equals( field.getName() ) )
245 {
246 return false;
247 }
248 if ( InputLocation.class.equals( fieldType ) )
249 {
250 return false;
251 }
252
253
254 if ( fieldType.isPrimitive() )
255 {
256 return false;
257 }
258
259 return !"parent".equals( field.getName() );
260 }
261
262 CacheItem( Class clazz )
263 {
264 this.isQualifiedForInterpolation = isQualifiedForInterpolation( clazz );
265 this.isArray = clazz.isArray();
266 List<CacheField> fields = new ArrayList<>();
267 if ( isQualifiedForInterpolation )
268 {
269 for ( Field currentField : clazz.getDeclaredFields() )
270 {
271 Class<?> type = currentField.getType();
272 if ( isQualifiedForInterpolation( currentField, type ) )
273 {
274 if ( String.class == type )
275 {
276 if ( !Modifier.isFinal( currentField.getModifiers() ) )
277 {
278 fields.add( new StringField( currentField ) );
279 }
280 }
281 else if ( List.class.isAssignableFrom( type ) )
282 {
283 fields.add( new ListField( currentField ) );
284 }
285 else if ( Collection.class.isAssignableFrom( type ) )
286 {
287 throw new RuntimeException(
288 "We dont interpolate into collections, use a list instead" );
289 }
290 else if ( Map.class.isAssignableFrom( type ) )
291 {
292 fields.add( new MapField( currentField ) );
293 }
294 else
295 {
296 fields.add( new ObjectField( currentField ) );
297 }
298 }
299 }
300 }
301 this.fields = fields.toArray( new CacheField[0] );
302 }
303
304 void interpolate( Object target, InterpolateObjectAction interpolateObjectAction )
305 {
306 for ( CacheField field : fields )
307 {
308 field.interpolate( target, interpolateObjectAction );
309 }
310 }
311
312 boolean isArray()
313 {
314 return isArray;
315 }
316 }
317
318 abstract static class CacheField
319 {
320 final Field field;
321
322 CacheField( Field field )
323 {
324 this.field = field;
325 field.setAccessible( true );
326 }
327
328 void interpolate( Object target, InterpolateObjectAction interpolateObjectAction )
329 {
330 try
331 {
332 doInterpolate( target, interpolateObjectAction );
333 }
334 catch ( IllegalArgumentException e )
335 {
336 interpolateObjectAction.problems.add(
337 new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ).setMessage(
338 "Failed to interpolate field3: " + field + " on class: "
339 + field.getType().getName() ).setException(
340 e ) );
341 }
342 catch ( IllegalAccessException e )
343 {
344 interpolateObjectAction.problems.add(
345 new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ).setMessage(
346 "Failed to interpolate field4: " + field + " on class: "
347 + field.getType().getName() ).setException( e ) );
348 }
349 }
350
351 abstract void doInterpolate( Object target, InterpolateObjectAction ctx )
352 throws IllegalAccessException;
353 }
354
355 static final class StringField
356 extends CacheField
357 {
358 StringField( Field field )
359 {
360 super( field );
361 }
362
363 @Override
364 void doInterpolate( Object target, InterpolateObjectAction ctx )
365 throws IllegalAccessException
366 {
367 String value = (String) field.get( target );
368 if ( value == null )
369 {
370 return;
371 }
372
373 String interpolated = ctx.interpolate( value );
374
375 if ( !interpolated.equals( value ) )
376 {
377 field.set( target, interpolated );
378 }
379 }
380 }
381
382 static final class ListField
383 extends CacheField
384 {
385 ListField( Field field )
386 {
387 super( field );
388 }
389
390 @Override
391 void doInterpolate( Object target, InterpolateObjectAction ctx )
392 throws IllegalAccessException
393 {
394 @SuppressWarnings( "unchecked" ) List<Object> c = (List<Object>) field.get( target );
395 if ( c == null )
396 {
397 return;
398 }
399
400 for ( int i = 0, size = c.size(); i < size; i++ )
401 {
402 Object value = c.get( i );
403
404 if ( value != null )
405 {
406 if ( String.class == value.getClass() )
407 {
408 String interpolated = ctx.interpolate( (String) value );
409
410 if ( !interpolated.equals( value ) )
411 {
412 try
413 {
414 c.set( i, interpolated );
415 }
416 catch ( UnsupportedOperationException e )
417 {
418 return;
419 }
420 }
421 }
422 else
423 {
424 if ( value.getClass().isArray() )
425 {
426 evaluateArray( value, ctx );
427 }
428 else
429 {
430 ctx.interpolationTargets.add( value );
431 }
432 }
433 }
434 }
435 }
436 }
437
438 static final class MapField
439 extends CacheField
440 {
441 MapField( Field field )
442 {
443 super( field );
444 }
445
446 @Override
447 void doInterpolate( Object target, InterpolateObjectAction ctx )
448 throws IllegalAccessException
449 {
450 @SuppressWarnings( "unchecked" ) Map<Object, Object> m = (Map<Object, Object>) field.get( target );
451 if ( m == null || m.isEmpty() )
452 {
453 return;
454 }
455
456 for ( Map.Entry<Object, Object> entry : m.entrySet() )
457 {
458 Object value = entry.getValue();
459
460 if ( value == null )
461 {
462 continue;
463 }
464
465 if ( String.class == value.getClass() )
466 {
467 String interpolated = ctx.interpolate( (String) value );
468
469 if ( !interpolated.equals( value ) )
470 {
471 try
472 {
473 entry.setValue( interpolated );
474 }
475 catch ( UnsupportedOperationException ignore )
476 {
477
478 }
479 }
480 }
481 else if ( value.getClass().isArray() )
482 {
483 evaluateArray( value, ctx );
484 }
485 else
486 {
487 ctx.interpolationTargets.add( value );
488 }
489 }
490 }
491 }
492
493 static final class ObjectField
494 extends CacheField
495 {
496 private final boolean isArray;
497
498 ObjectField( Field field )
499 {
500 super( field );
501 this.isArray = field.getType().isArray();
502 }
503
504 @Override
505 void doInterpolate( Object target, InterpolateObjectAction ctx )
506 throws IllegalAccessException
507 {
508 Object value = field.get( target );
509 if ( value != null )
510 {
511 if ( isArray )
512 {
513 evaluateArray( value, ctx );
514 }
515 else
516 {
517 ctx.interpolationTargets.add( value );
518 }
519 }
520 }
521 }
522 }
523 }