View Javadoc
1   package org.apache.maven.model.interpolation;
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.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   * StringSearchModelInterpolator
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      // Empirical data from 3.x, actual =40
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                 //noinspection SimplifiableIfStatement
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 ) ); // TODO Not entirely the same message
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                                 // nop
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 }