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.shared.filtering;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.Reader;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.LinkedHashSet;
27  import java.util.List;
28  import java.util.Properties;
29  import java.util.TreeSet;
30  
31  import org.apache.maven.execution.MavenSession;
32  import org.apache.maven.project.MavenProject;
33  import org.apache.maven.settings.Settings;
34  import org.codehaus.plexus.interpolation.Interpolator;
35  import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
36  import org.codehaus.plexus.interpolation.PrefixedObjectValueSource;
37  import org.codehaus.plexus.interpolation.PropertiesBasedValueSource;
38  import org.codehaus.plexus.interpolation.RecursionInterceptor;
39  import org.codehaus.plexus.interpolation.SimpleRecursionInterceptor;
40  import org.codehaus.plexus.interpolation.SingleResponseValueSource;
41  import org.codehaus.plexus.interpolation.ValueSource;
42  import org.codehaus.plexus.interpolation.multi.MultiDelimiterStringSearchInterpolator;
43  import org.slf4j.Logger;
44  import org.slf4j.LoggerFactory;
45  
46  class BaseFilter implements DefaultFilterInfo {
47      private final Logger logger = LoggerFactory.getLogger(getClass());
48  
49      protected Logger getLogger() {
50          return logger;
51      }
52  
53      @Override
54      public List<FilterWrapper> getDefaultFilterWrappers(
55              final MavenProject mavenProject,
56              List<String> filters,
57              final boolean escapedBackslashesInFilePath,
58              MavenSession mavenSession,
59              MavenResourcesExecution mavenResourcesExecution)
60              throws MavenFilteringException {
61  
62          MavenResourcesExecution mre =
63                  mavenResourcesExecution == null ? new MavenResourcesExecution() : mavenResourcesExecution.copyOf();
64  
65          mre.setMavenProject(mavenProject);
66          mre.setMavenSession(mavenSession);
67          mre.setFilters(filters);
68          mre.setEscapedBackslashesInFilePath(escapedBackslashesInFilePath);
69  
70          return getDefaultFilterWrappers(mre);
71      }
72  
73      @Override
74      public List<FilterWrapper> getDefaultFilterWrappers(final AbstractMavenFilteringRequest request)
75              throws MavenFilteringException {
76          // backup values
77          boolean supportMultiLineFiltering = request.isSupportMultiLineFiltering();
78  
79          request.setSupportMultiLineFiltering(supportMultiLineFiltering);
80  
81          // Here we build some properties which will be used to read some properties files
82          // to interpolate the expression ${ } in this properties file
83  
84          // Take a copy of filterProperties to ensure that evaluated filterTokens are not propagated
85          // to subsequent filter files. Note: this replicates current behaviour and seems to make sense.
86  
87          final Properties baseProps = new Properties();
88  
89          // Project properties
90          if (request.getMavenProject() != null) {
91              baseProps.putAll(
92                      request.getMavenProject().getProperties() == null
93                              ? Collections.emptyMap()
94                              : request.getMavenProject().getProperties());
95          }
96          // TODO this is NPE free but do we consider this as normal
97          // or do we have to throw an MavenFilteringException with mavenSession cannot be null
98          //
99          // khmarbaise: 2016-05-21:
100         // If we throw an MavenFilteringException tests will fail which is
101         // caused by for example:
102         // void copyFile( File from, final File to, boolean filtering, List<FileUtils.FilterWrapper> filterWrappers,
103         // String encoding )
104         // in MavenFileFilter interface where no MavenSession is given.
105         // So changing here to throw a MavenFilteringException would make
106         // it necessary to change the interface or we need to find a better solution.
107         //
108         if (request.getMavenSession() != null) {
109             // User properties have precedence over system properties
110             baseProps.putAll(request.getMavenSession().getSystemProperties());
111             baseProps.putAll(request.getMavenSession().getUserProperties());
112         }
113 
114         // now we build properties to use for resources interpolation
115 
116         final Properties filterProperties = new Properties();
117 
118         File basedir =
119                 request.getMavenProject() != null ? request.getMavenProject().getBasedir() : new File(".");
120 
121         loadProperties(filterProperties, basedir, request.getFileFilters(), baseProps);
122         if (filterProperties.size() < 1) {
123             filterProperties.putAll(baseProps);
124         }
125 
126         if (request.getMavenProject() != null) {
127             if (request.isInjectProjectBuildFilters()) {
128                 List<String> buildFilters =
129                         new ArrayList<>(request.getMavenProject().getBuild().getFilters());
130 
131                 // JDK-8015656: (coll) unexpected NPE from removeAll
132                 if (request.getFileFilters() != null) {
133                     buildFilters.removeAll(request.getFileFilters());
134                 }
135 
136                 loadProperties(filterProperties, basedir, buildFilters, baseProps);
137             }
138 
139             // Project properties
140             filterProperties.putAll(
141                     request.getMavenProject().getProperties() == null
142                             ? Collections.emptyMap()
143                             : request.getMavenProject().getProperties());
144         }
145         if (request.getMavenSession() != null) {
146             // User properties have precedence over system properties
147             filterProperties.putAll(request.getMavenSession().getSystemProperties());
148             filterProperties.putAll(request.getMavenSession().getUserProperties());
149         }
150 
151         if (request.getAdditionalProperties() != null) {
152             // additional properties wins
153             filterProperties.putAll(request.getAdditionalProperties());
154         }
155 
156         List<FilterWrapper> defaultFilterWrappers =
157                 new ArrayList<>(request.getDelimiters().size() + 1);
158 
159         if (getLogger().isDebugEnabled()) {
160             getLogger().debug("properties used:");
161             for (String s : new TreeSet<>(filterProperties.stringPropertyNames())) {
162                 getLogger().debug(s + ": " + filterProperties.getProperty(s));
163             }
164         }
165 
166         final ValueSource propertiesValueSource = new PropertiesBasedValueSource(filterProperties);
167 
168         FilterWrapper wrapper = new Wrapper(
169                 request.getDelimiters(),
170                 request.getMavenProject(),
171                 request.getMavenSession(),
172                 propertiesValueSource,
173                 request.getProjectStartExpressions(),
174                 request.getEscapeString(),
175                 request.isEscapeWindowsPaths(),
176                 request.isSupportMultiLineFiltering());
177 
178         defaultFilterWrappers.add(wrapper);
179 
180         return defaultFilterWrappers;
181     }
182 
183     /**
184      * default visibility only for testing reason !
185      */
186     void loadProperties(
187             Properties filterProperties, File basedir, List<String> propertiesFilePaths, Properties baseProps)
188             throws MavenFilteringException {
189         if (propertiesFilePaths != null) {
190             Properties workProperties = new Properties();
191             workProperties.putAll(baseProps);
192 
193             for (String filterFile : propertiesFilePaths) {
194                 if (filterFile == null || filterFile.trim().isEmpty()) {
195                     // skip empty file name
196                     continue;
197                 }
198                 try {
199                     File propFile = FilteringUtils.resolveFile(basedir, filterFile);
200                     Properties properties = PropertyUtils.loadPropertyFile(propFile, workProperties, getLogger());
201                     filterProperties.putAll(properties);
202                     workProperties.putAll(properties);
203                 } catch (IOException e) {
204                     throw new MavenFilteringException("Error loading property file '" + filterFile + "'", e);
205                 }
206             }
207         }
208     }
209 
210     private static final class Wrapper extends FilterWrapper {
211 
212         private LinkedHashSet<String> delimiters;
213 
214         private MavenProject project;
215 
216         private ValueSource propertiesValueSource;
217 
218         private List<String> projectStartExpressions;
219 
220         private String escapeString;
221 
222         private boolean escapeWindowsPaths;
223 
224         private final MavenSession mavenSession;
225 
226         private boolean supportMultiLineFiltering;
227 
228         Wrapper(
229                 LinkedHashSet<String> delimiters,
230                 MavenProject project,
231                 MavenSession mavenSession,
232                 ValueSource propertiesValueSource,
233                 List<String> projectStartExpressions,
234                 String escapeString,
235                 boolean escapeWindowsPaths,
236                 boolean supportMultiLineFiltering) {
237             super();
238             this.delimiters = delimiters;
239             this.project = project;
240             this.mavenSession = mavenSession;
241             this.propertiesValueSource = propertiesValueSource;
242             this.projectStartExpressions = projectStartExpressions;
243             this.escapeString = escapeString;
244             this.escapeWindowsPaths = escapeWindowsPaths;
245             this.supportMultiLineFiltering = supportMultiLineFiltering;
246         }
247 
248         @Override
249         public Reader getReader(Reader reader) {
250             Interpolator interpolator = createInterpolator(
251                     delimiters,
252                     projectStartExpressions,
253                     propertiesValueSource,
254                     project,
255                     mavenSession,
256                     escapeString,
257                     escapeWindowsPaths);
258 
259             MultiDelimiterInterpolatorFilterReaderLineEnding filterReader =
260                     new MultiDelimiterInterpolatorFilterReaderLineEnding(
261                             reader, interpolator, supportMultiLineFiltering);
262 
263             final RecursionInterceptor ri;
264             if (projectStartExpressions != null && !projectStartExpressions.isEmpty()) {
265                 ri = new PrefixAwareRecursionInterceptor(projectStartExpressions, true);
266             } else {
267                 ri = new SimpleRecursionInterceptor();
268             }
269 
270             filterReader.setRecursionInterceptor(ri);
271             filterReader.setDelimiterSpecs(delimiters);
272 
273             filterReader.setInterpolateWithPrefixPattern(false);
274             filterReader.setEscapeString(escapeString);
275 
276             return filterReader;
277         }
278     }
279 
280     private static Interpolator createInterpolator(
281             LinkedHashSet<String> delimiters,
282             List<String> projectStartExpressions,
283             ValueSource propertiesValueSource,
284             MavenProject project,
285             MavenSession mavenSession,
286             String escapeString,
287             boolean escapeWindowsPaths) {
288         MultiDelimiterStringSearchInterpolator interpolator = new MultiDelimiterStringSearchInterpolator();
289         interpolator.setDelimiterSpecs(delimiters);
290 
291         interpolator.addValueSource(propertiesValueSource);
292 
293         if (project != null) {
294             interpolator.addValueSource(new PrefixedObjectValueSource(projectStartExpressions, project, true));
295         }
296 
297         if (mavenSession != null) {
298             interpolator.addValueSource(new PrefixedObjectValueSource("session", mavenSession));
299 
300             final Settings settings = mavenSession.getSettings();
301             if (settings != null) {
302                 interpolator.addValueSource(new PrefixedObjectValueSource("settings", settings));
303                 interpolator.addValueSource(
304                         new SingleResponseValueSource("localRepository", settings.getLocalRepository()));
305             }
306         }
307 
308         interpolator.setEscapeString(escapeString);
309 
310         if (escapeWindowsPaths) {
311             interpolator.addPostProcessor((expression, value) ->
312                     (value instanceof String) ? FilteringUtils.escapeWindowsPath((String) value) : value);
313         }
314         return interpolator;
315     }
316 }