View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.model.interpolation;
20  
21  import java.io.File;
22  import java.lang.reflect.Array;
23  import java.lang.reflect.Field;
24  import java.lang.reflect.Modifier;
25  import java.security.AccessController;
26  import java.security.PrivilegedAction;
27  import java.util.ArrayList;
28  import java.util.Collection;
29  import java.util.HashMap;
30  import java.util.LinkedList;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.concurrent.ConcurrentHashMap;
34  
35  import org.apache.maven.model.InputLocation;
36  import org.apache.maven.model.Model;
37  import org.apache.maven.model.building.ModelBuildingRequest;
38  import org.apache.maven.model.building.ModelProblem.Severity;
39  import org.apache.maven.model.building.ModelProblem.Version;
40  import org.apache.maven.model.building.ModelProblemCollector;
41  import org.apache.maven.model.building.ModelProblemCollectorRequest;
42  import org.codehaus.plexus.interpolation.InterpolationException;
43  import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
44  import org.codehaus.plexus.interpolation.RecursionInterceptor;
45  import org.codehaus.plexus.interpolation.StringSearchInterpolator;
46  import org.codehaus.plexus.interpolation.ValueSource;
47  
48  /**
49   * StringSearchModelInterpolator
50   * @deprecated replaced by StringVisitorModelInterpolator (MNG-6697)
51   */
52  @Deprecated
53  public class StringSearchModelInterpolator extends AbstractStringBasedModelInterpolator {
54      private static final Map<Class<?>, InterpolateObjectAction.CacheItem> CACHED_ENTRIES =
55              new ConcurrentHashMap<>(80, 0.75f, 2);
56      // Empirical data from 3.x, actual =40
57  
58      private interface InnerInterpolator {
59          String interpolate(String value);
60      }
61  
62      @Override
63      public Model interpolateModel(
64              Model model, File projectDir, ModelBuildingRequest config, ModelProblemCollector problems) {
65          interpolateObject(model, model, projectDir, config, problems);
66          return model;
67      }
68  
69      void interpolateObject(
70              Object obj, Model model, File projectDir, ModelBuildingRequest config, ModelProblemCollector problems) {
71          List<? extends ValueSource> valueSources = createValueSources(model, projectDir, config, problems);
72          List<? extends InterpolationPostProcessor> postProcessors = createPostProcessors(model, projectDir, config);
73  
74          InnerInterpolator innerInterpolator = createInterpolator(valueSources, postProcessors, problems);
75  
76          PrivilegedAction<Object> action = new InterpolateObjectAction(obj, innerInterpolator, problems);
77          AccessController.doPrivileged(action);
78      }
79  
80      private InnerInterpolator createInterpolator(
81              List<? extends ValueSource> valueSources,
82              List<? extends InterpolationPostProcessor> postProcessors,
83              final ModelProblemCollector problems) {
84          final Map<String, String> cache = new HashMap<>();
85          final StringSearchInterpolator interpolator = new StringSearchInterpolator();
86          interpolator.setCacheAnswers(true);
87          for (ValueSource vs : valueSources) {
88              interpolator.addValueSource(vs);
89          }
90          for (InterpolationPostProcessor postProcessor : postProcessors) {
91              interpolator.addPostProcessor(postProcessor);
92          }
93          final RecursionInterceptor recursionInterceptor = createRecursionInterceptor();
94          return new InnerInterpolator() {
95              @Override
96              public String interpolate(String value) {
97                  if (value != null && value.contains("${")) {
98                      String c = cache.get(value);
99                      if (c == null) {
100                         try {
101                             c = interpolator.interpolate(value, recursionInterceptor);
102                         } catch (InterpolationException e) {
103                             problems.add(new ModelProblemCollectorRequest(Severity.ERROR, Version.BASE)
104                                     .setMessage(e.getMessage())
105                                     .setException(e));
106                         }
107                         cache.put(value, c);
108                     }
109                     return c;
110                 }
111                 return value;
112             }
113         };
114     }
115 
116     private static final class InterpolateObjectAction implements PrivilegedAction<Object> {
117         private final LinkedList<Object> interpolationTargets;
118 
119         private final InnerInterpolator interpolator;
120 
121         private final ModelProblemCollector problems;
122 
123         InterpolateObjectAction(Object target, InnerInterpolator interpolator, ModelProblemCollector problems) {
124             this.interpolationTargets = new LinkedList<>();
125             interpolationTargets.add(target);
126             this.interpolator = interpolator;
127             this.problems = problems;
128         }
129 
130         @Override
131         public Object run() {
132             while (!interpolationTargets.isEmpty()) {
133                 Object obj = interpolationTargets.removeFirst();
134 
135                 traverseObjectWithParents(obj.getClass(), obj);
136             }
137             return null;
138         }
139 
140         private String interpolate(String value) {
141             return interpolator.interpolate(value);
142         }
143 
144         private void traverseObjectWithParents(Class<?> cls, Object target) {
145             if (cls == null) {
146                 return;
147             }
148 
149             CacheItem cacheEntry = getCacheEntry(cls);
150             if (cacheEntry.isArray()) {
151                 evaluateArray(target, this);
152             } else if (cacheEntry.isQualifiedForInterpolation) {
153                 cacheEntry.interpolate(target, this);
154 
155                 traverseObjectWithParents(cls.getSuperclass(), target);
156             }
157         }
158 
159         private CacheItem getCacheEntry(Class<?> cls) {
160             CacheItem cacheItem = CACHED_ENTRIES.get(cls);
161             if (cacheItem == null) {
162                 cacheItem = new CacheItem(cls);
163                 CACHED_ENTRIES.put(cls, cacheItem);
164             }
165             return cacheItem;
166         }
167 
168         private static void evaluateArray(Object target, InterpolateObjectAction ctx) {
169             int len = Array.getLength(target);
170             for (int i = 0; i < len; i++) {
171                 Object value = Array.get(target, i);
172                 if (value != null) {
173                     if (String.class == value.getClass()) {
174                         String interpolated = ctx.interpolate((String) value);
175 
176                         if (!interpolated.equals(value)) {
177                             Array.set(target, i, interpolated);
178                         }
179                     } else {
180                         ctx.interpolationTargets.add(value);
181                     }
182                 }
183             }
184         }
185 
186         private static class CacheItem {
187             private final boolean isArray;
188 
189             private final boolean isQualifiedForInterpolation;
190 
191             private final CacheField[] fields;
192 
193             private boolean isQualifiedForInterpolation(Class<?> cls) {
194                 Package pkg = cls.getPackage();
195                 if (pkg == null) {
196                     return true;
197                 }
198                 String pkgName = pkg.getName();
199                 return !pkgName.startsWith("java.") && !pkgName.startsWith("javax.");
200             }
201 
202             private boolean isQualifiedForInterpolation(Field field, Class<?> fieldType) {
203                 if (Map.class.equals(fieldType) && "locations".equals(field.getName())) {
204                     return false;
205                 }
206                 if (InputLocation.class.equals(fieldType)) {
207                     return false;
208                 }
209 
210                 //noinspection SimplifiableIfStatement
211                 if (fieldType.isPrimitive()) {
212                     return false;
213                 }
214 
215                 return !"parent".equals(field.getName());
216             }
217 
218             CacheItem(Class clazz) {
219                 this.isQualifiedForInterpolation = isQualifiedForInterpolation(clazz);
220                 this.isArray = clazz.isArray();
221                 List<CacheField> fields = new ArrayList<>();
222                 if (isQualifiedForInterpolation) {
223                     for (Field currentField : clazz.getDeclaredFields()) {
224                         Class<?> type = currentField.getType();
225                         if (isQualifiedForInterpolation(currentField, type)) {
226                             if (String.class == type) {
227                                 if (!Modifier.isFinal(currentField.getModifiers())) {
228                                     fields.add(new StringField(currentField));
229                                 }
230                             } else if (List.class.isAssignableFrom(type)) {
231                                 fields.add(new ListField(currentField));
232                             } else if (Collection.class.isAssignableFrom(type)) {
233                                 throw new RuntimeException("We dont interpolate into collections, use a list instead");
234                             } else if (Map.class.isAssignableFrom(type)) {
235                                 fields.add(new MapField(currentField));
236                             } else {
237                                 fields.add(new ObjectField(currentField));
238                             }
239                         }
240                     }
241                 }
242                 this.fields = fields.toArray(new CacheField[0]);
243             }
244 
245             void interpolate(Object target, InterpolateObjectAction interpolateObjectAction) {
246                 for (CacheField field : fields) {
247                     field.interpolate(target, interpolateObjectAction);
248                 }
249             }
250 
251             boolean isArray() {
252                 return isArray;
253             }
254         }
255 
256         abstract static class CacheField {
257             final Field field;
258 
259             CacheField(Field field) {
260                 this.field = field;
261                 field.setAccessible(true);
262             }
263 
264             void interpolate(Object target, InterpolateObjectAction interpolateObjectAction) {
265                 try {
266                     doInterpolate(target, interpolateObjectAction);
267                 } catch (IllegalArgumentException e) {
268                     interpolateObjectAction.problems.add(new ModelProblemCollectorRequest(Severity.ERROR, Version.BASE)
269                             .setMessage("Failed to interpolate field3: " + field + " on class: "
270                                     + field.getType().getName())
271                             .setException(e)); // TODO Not entirely the same message
272                 } catch (IllegalAccessException e) {
273                     interpolateObjectAction.problems.add(new ModelProblemCollectorRequest(Severity.ERROR, Version.BASE)
274                             .setMessage("Failed to interpolate field4: " + field + " on class: "
275                                     + field.getType().getName())
276                             .setException(e));
277                 }
278             }
279 
280             abstract void doInterpolate(Object target, InterpolateObjectAction ctx) throws IllegalAccessException;
281         }
282 
283         static final class StringField extends CacheField {
284             StringField(Field field) {
285                 super(field);
286             }
287 
288             @Override
289             void doInterpolate(Object target, InterpolateObjectAction ctx) throws IllegalAccessException {
290                 String value = (String) field.get(target);
291                 if (value == null) {
292                     return;
293                 }
294 
295                 String interpolated = ctx.interpolate(value);
296 
297                 if (interpolated != null && !interpolated.equals(value)) {
298                     field.set(target, interpolated);
299                 }
300             }
301         }
302 
303         static final class ListField extends CacheField {
304             ListField(Field field) {
305                 super(field);
306             }
307 
308             @Override
309             void doInterpolate(Object target, InterpolateObjectAction ctx) throws IllegalAccessException {
310                 @SuppressWarnings("unchecked")
311                 List<Object> c = (List<Object>) field.get(target);
312                 if (c == null) {
313                     return;
314                 }
315 
316                 for (int i = 0, size = c.size(); i < size; i++) {
317                     Object value = c.get(i);
318 
319                     if (value != null) {
320                         if (String.class == value.getClass()) {
321                             String interpolated = ctx.interpolate((String) value);
322 
323                             if (!interpolated.equals(value)) {
324                                 try {
325                                     c.set(i, interpolated);
326                                 } catch (UnsupportedOperationException e) {
327                                     return;
328                                 }
329                             }
330                         } else {
331                             if (value.getClass().isArray()) {
332                                 evaluateArray(value, ctx);
333                             } else {
334                                 ctx.interpolationTargets.add(value);
335                             }
336                         }
337                     }
338                 }
339             }
340         }
341 
342         static final class MapField extends CacheField {
343             MapField(Field field) {
344                 super(field);
345             }
346 
347             @Override
348             void doInterpolate(Object target, InterpolateObjectAction ctx) throws IllegalAccessException {
349                 @SuppressWarnings("unchecked")
350                 Map<Object, Object> m = (Map<Object, Object>) field.get(target);
351                 if (m == null || m.isEmpty()) {
352                     return;
353                 }
354 
355                 for (Map.Entry<Object, Object> entry : m.entrySet()) {
356                     Object value = entry.getValue();
357 
358                     if (value == null) {
359                         continue;
360                     }
361 
362                     if (String.class == value.getClass()) {
363                         String interpolated = ctx.interpolate((String) value);
364 
365                         if (interpolated != null && !interpolated.equals(value)) {
366                             try {
367                                 entry.setValue(interpolated);
368                             } catch (UnsupportedOperationException ignore) {
369                                 // nop
370                             }
371                         }
372                     } else if (value.getClass().isArray()) {
373                         evaluateArray(value, ctx);
374                     } else {
375                         ctx.interpolationTargets.add(value);
376                     }
377                 }
378             }
379         }
380 
381         static final class ObjectField extends CacheField {
382             private final boolean isArray;
383 
384             ObjectField(Field field) {
385                 super(field);
386                 this.isArray = field.getType().isArray();
387             }
388 
389             @Override
390             void doInterpolate(Object target, InterpolateObjectAction ctx) throws IllegalAccessException {
391                 Object value = field.get(target);
392                 if (value != null) {
393                     if (isArray) {
394                         evaluateArray(value, ctx);
395                     } else {
396                         ctx.interpolationTargets.add(value);
397                     }
398                 }
399             }
400         }
401     }
402 }