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