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             putAll(baseProps, request.getMavenSession().getSystemProperties());
111             putAll(baseProps, 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.isEmpty()) {
123             putAll(filterProperties, 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             putAll(filterProperties, request.getMavenSession().getSystemProperties());
148             putAll(filterProperties, request.getMavenSession().getUserProperties());
149         }
150 
151         if (request.getAdditionalProperties() != null) {
152             // additional properties wins
153             putAll(filterProperties, 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     @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
184     private static void putAll(Properties filterProperties, Properties request) {
185         synchronized (request) {
186             filterProperties.putAll(request);
187         }
188     }
189 
190     /**
191      * default visibility only for testing reason !
192      */
193     void loadProperties(
194             Properties filterProperties, File basedir, List<String> propertiesFilePaths, Properties baseProps)
195             throws MavenFilteringException {
196         if (propertiesFilePaths != null) {
197             Properties workProperties = new Properties();
198             putAll(workProperties, baseProps);
199 
200             for (String filterFile : propertiesFilePaths) {
201                 if (filterFile == null || filterFile.trim().isEmpty()) {
202                     // skip empty file name
203                     continue;
204                 }
205                 try {
206                     File propFile = FilteringUtils.resolveFile(basedir, filterFile);
207                     Properties properties = PropertyUtils.loadPropertyFile(propFile, workProperties, getLogger());
208                     putAll(filterProperties, properties);
209                     putAll(workProperties, properties);
210                 } catch (IOException e) {
211                     throw new MavenFilteringException("Error loading property file '" + filterFile + "'", e);
212                 }
213             }
214         }
215     }
216 
217     private static final class Wrapper extends FilterWrapper {
218 
219         private LinkedHashSet<String> delimiters;
220 
221         private MavenProject project;
222 
223         private ValueSource propertiesValueSource;
224 
225         private List<String> projectStartExpressions;
226 
227         private String escapeString;
228 
229         private boolean escapeWindowsPaths;
230 
231         private final MavenSession mavenSession;
232 
233         private boolean supportMultiLineFiltering;
234 
235         Wrapper(
236                 LinkedHashSet<String> delimiters,
237                 MavenProject project,
238                 MavenSession mavenSession,
239                 ValueSource propertiesValueSource,
240                 List<String> projectStartExpressions,
241                 String escapeString,
242                 boolean escapeWindowsPaths,
243                 boolean supportMultiLineFiltering) {
244             super();
245             this.delimiters = delimiters;
246             this.project = project;
247             this.mavenSession = mavenSession;
248             this.propertiesValueSource = propertiesValueSource;
249             this.projectStartExpressions = projectStartExpressions;
250             this.escapeString = escapeString;
251             this.escapeWindowsPaths = escapeWindowsPaths;
252             this.supportMultiLineFiltering = supportMultiLineFiltering;
253         }
254 
255         @Override
256         public Reader getReader(Reader reader) {
257             Interpolator interpolator = createInterpolator(
258                     delimiters,
259                     projectStartExpressions,
260                     propertiesValueSource,
261                     project,
262                     mavenSession,
263                     escapeString,
264                     escapeWindowsPaths);
265 
266             MultiDelimiterInterpolatorFilterReaderLineEnding filterReader =
267                     new MultiDelimiterInterpolatorFilterReaderLineEnding(
268                             reader, interpolator, supportMultiLineFiltering);
269 
270             final RecursionInterceptor ri;
271             if (projectStartExpressions != null && !projectStartExpressions.isEmpty()) {
272                 ri = new PrefixAwareRecursionInterceptor(projectStartExpressions, true);
273             } else {
274                 ri = new SimpleRecursionInterceptor();
275             }
276 
277             filterReader.setRecursionInterceptor(ri);
278             filterReader.setDelimiterSpecs(delimiters);
279 
280             filterReader.setInterpolateWithPrefixPattern(false);
281             filterReader.setEscapeString(escapeString);
282 
283             return filterReader;
284         }
285     }
286 
287     private static Interpolator createInterpolator(
288             LinkedHashSet<String> delimiters,
289             List<String> projectStartExpressions,
290             ValueSource propertiesValueSource,
291             MavenProject project,
292             MavenSession mavenSession,
293             String escapeString,
294             boolean escapeWindowsPaths) {
295         MultiDelimiterStringSearchInterpolator interpolator = new MultiDelimiterStringSearchInterpolator();
296         interpolator.setDelimiterSpecs(delimiters);
297 
298         interpolator.addValueSource(propertiesValueSource);
299 
300         if (project != null) {
301             interpolator.addValueSource(new PrefixedObjectValueSource(projectStartExpressions, project, true));
302         }
303 
304         if (mavenSession != null) {
305             interpolator.addValueSource(new PrefixedObjectValueSource("session", mavenSession));
306 
307             final Settings settings = mavenSession.getSettings();
308             if (settings != null) {
309                 interpolator.addValueSource(new PrefixedObjectValueSource("settings", settings));
310                 interpolator.addValueSource(
311                         new SingleResponseValueSource("localRepository", settings.getLocalRepository()));
312             }
313         }
314 
315         interpolator.setEscapeString(escapeString);
316 
317         if (escapeWindowsPaths) {
318             interpolator.addPostProcessor((expression, value) ->
319                     (value instanceof String) ? FilteringUtils.escapeWindowsPath((String) value) : value);
320         }
321         return interpolator;
322     }
323 }