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