1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 package org.polago.maven.plugins.mergeproperties;
37
38 import java.io.File;
39 import java.io.FileInputStream;
40 import java.io.FileWriter;
41 import java.io.IOException;
42 import java.io.InputStream;
43 import java.io.InputStreamReader;
44 import java.io.Reader;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.List;
48 import java.util.Map.Entry;
49 import java.util.Properties;
50 import java.util.TreeMap;
51
52 import org.apache.commons.configuration2.Configuration;
53 import org.apache.commons.configuration2.MapConfiguration;
54 import org.apache.commons.configuration2.PropertiesConfiguration;
55 import org.apache.commons.configuration2.PropertiesConfigurationLayout;
56 import org.apache.commons.configuration2.ex.ConfigurationException;
57 import org.apache.commons.io.FilenameUtils;
58 import org.apache.maven.execution.MavenSession;
59 import org.apache.maven.model.Resource;
60 import org.apache.maven.project.MavenProject;
61 import org.apache.maven.shared.filtering.MavenFileFilter;
62 import org.apache.maven.shared.filtering.MavenFilteringException;
63 import org.apache.maven.shared.filtering.MavenResourcesExecution;
64 import org.apache.maven.shared.filtering.MavenResourcesFiltering;
65 import org.apache.maven.shared.utils.PathTool;
66 import org.apache.maven.shared.utils.ReaderFactory;
67 import org.apache.maven.shared.utils.StringUtils;
68 import org.apache.maven.shared.utils.io.FileUtils;
69 import org.apache.maven.shared.utils.io.FileUtils.FilterWrapper;
70 import org.codehaus.plexus.component.annotations.Component;
71 import org.codehaus.plexus.component.annotations.Requirement;
72 import org.codehaus.plexus.logging.AbstractLogEnabled;
73 import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
74 import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
75 import org.codehaus.plexus.util.IOUtil;
76 import org.codehaus.plexus.util.Scanner;
77 import org.sonatype.plexus.build.incremental.BuildContext;
78
79
80
81
82 @Component(role = MavenResourcesFiltering.class, hint = "merge")
83 public class MergeProperitesMavenResourcesFiltering extends AbstractLogEnabled
84 implements MavenResourcesFiltering, Initializable {
85
86 private static final String[] EMPTY_STRING_ARRAY = {};
87
88 private static final String[] DEFAULT_INCLUDES = {"**/**.properties"};
89
90 private List<String> defaultNonFilteredFileExtensions;
91
92 @Requirement(hint = "default")
93 private MavenFileFilter mavenFileFilter;
94
95 @Requirement
96 private BuildContext buildContext;
97
98 @Requirement
99 private MavenSession session;
100
101 @Requirement
102 private MavenProject project;
103
104 private String outputFile;
105
106 private boolean overwriteProperties = false;
107
108
109
110
111 @Override
112 public void initialize() throws InitializationException {
113
114 this.defaultNonFilteredFileExtensions = new ArrayList<String>();
115 }
116
117
118
119
120 @Override
121 public boolean filteredFileExtension(String fileName, List<String> userNonFilteredFileExtensions) {
122 List<String> nonFilteredFileExtensions = new ArrayList<String>(getDefaultNonFilteredFileExtensions());
123 if (userNonFilteredFileExtensions != null) {
124 nonFilteredFileExtensions.addAll(userNonFilteredFileExtensions);
125 }
126 boolean filteredFileExtension =
127 !nonFilteredFileExtensions.contains(StringUtils.lowerCase(FilenameUtils.getExtension(fileName)));
128 if (getLogger().isDebugEnabled()) {
129 getLogger().debug(
130 "file " + fileName + " has a" + (filteredFileExtension ? " " : " non ") + "filtered file extension");
131 }
132 return filteredFileExtension;
133 }
134
135
136
137
138 @Override
139 public List<String> getDefaultNonFilteredFileExtensions() {
140 if (this.defaultNonFilteredFileExtensions == null) {
141 this.defaultNonFilteredFileExtensions = new ArrayList<String>();
142 }
143 return this.defaultNonFilteredFileExtensions;
144 }
145
146
147
148
149 @Override
150 public void filterResources(MavenResourcesExecution mavenResourcesExecution) throws MavenFilteringException {
151
152 if (mavenResourcesExecution == null) {
153 throw new MavenFilteringException("mavenResourcesExecution cannot be null");
154 }
155
156 if (mavenResourcesExecution.getResources() == null) {
157 getLogger().info("No resources configured, skipping merging");
158 return;
159 }
160
161 if (mavenResourcesExecution.getOutputDirectory() == null) {
162 throw new MavenFilteringException("outputDirectory cannot be null");
163 }
164
165 if (mavenResourcesExecution.isUseDefaultFilterWrappers()) {
166 handleDefaultFilterWrappers(mavenResourcesExecution);
167 }
168
169 if (mavenResourcesExecution.getEncoding() == null || mavenResourcesExecution.getEncoding().length() < 1) {
170 getLogger().warn("Using platform encoding (" + ReaderFactory.FILE_ENCODING
171 + " actually) to merge properties, i.e. build is platform dependent!");
172 } else {
173 getLogger().info("Using '" + mavenResourcesExecution.getEncoding() + "' encoding to merge properties.");
174 }
175
176 Properties outputProperties = new Properties();
177 long lastModified = 0L;
178
179 for (Resource resource : mavenResourcesExecution.getResources()) {
180
181 if (getLogger().isDebugEnabled()) {
182 String ls = System.getProperty("line.separator");
183 StringBuffer debugMessage =
184 new StringBuffer("Resource with targetPath " + resource.getTargetPath()).append(ls);
185 debugMessage.append("directory " + resource.getDirectory()).append(ls);
186 debugMessage
187 .append(
188 "excludes " + (resource.getExcludes() == null ? " empty " : resource.getExcludes().toString()))
189 .append(ls);
190 debugMessage.append(
191 "includes " + (resource.getIncludes() == null ? " empty " : resource.getIncludes().toString()));
192 getLogger().debug(debugMessage.toString());
193 }
194
195 String targetPath = resource.getTargetPath();
196
197 File resourceDirectory = new File(resource.getDirectory());
198
199 if (!resourceDirectory.isAbsolute()) {
200 resourceDirectory =
201 new File(mavenResourcesExecution.getResourcesBaseDirectory(), resourceDirectory.getPath());
202 }
203
204 if (!resourceDirectory.exists()) {
205 getLogger().info("Skipping non-existing resourceDirectory: " + resourceDirectory.getPath());
206 continue;
207 }
208
209
210 File outputDirectory = mavenResourcesExecution.getOutputDirectory();
211 boolean outputExists = outputDirectory.exists();
212 if (!outputExists && !outputDirectory.mkdirs()) {
213 throw new MavenFilteringException("Cannot create resource output directory: " + outputDirectory);
214 }
215
216 boolean ignoreDelta = !outputExists || mavenResourcesExecution.isOverwrite()
217 || buildContext.hasDelta(mavenResourcesExecution.getFileFilters())
218 || buildContext.hasDelta(getRelativeOutputDirectory(mavenResourcesExecution));
219 getLogger().debug("ignoreDelta " + ignoreDelta);
220 Scanner scanner = buildContext.newScanner(resourceDirectory, ignoreDelta);
221
222 setupScanner(resource, scanner, mavenResourcesExecution.isAddDefaultExcludes());
223
224 scanner.scan();
225
226 List<String> includedFiles = Arrays.asList(scanner.getIncludedFiles());
227 if (!ignoreDelta && buildContext.isIncremental() && !includedFiles.isEmpty()) {
228
229
230 getLogger().debug("Reverting to full scan");
231
232 scanner = buildContext.newScanner(resourceDirectory, true);
233 setupScanner(resource, scanner, mavenResourcesExecution.isAddDefaultExcludes());
234 scanner.scan();
235 includedFiles = Arrays.asList(scanner.getIncludedFiles());
236 }
237
238 getLogger().info("Merging " + includedFiles.size() + " resource" + (includedFiles.size() > 1 ? "s" : "")
239 + (targetPath == null ? "" : " to " + targetPath));
240
241 for (String name : includedFiles) {
242 getLogger().debug("Processing file " + name);
243 File source = new File(resourceDirectory, name);
244 lastModified = Math.max(lastModified, source.lastModified());
245
246 boolean filteredExt =
247 filteredFileExtension(source.getName(), mavenResourcesExecution.getNonFilteredFileExtensions());
248
249 mergeProperties(outputProperties, source, resource.isFiltering() && filteredExt,
250 mavenResourcesExecution.getFilterWrappers(), mavenResourcesExecution.getEncoding(),
251 overwriteProperties);
252 }
253
254 }
255
256 File destinationFile = getDestinationFile(mavenResourcesExecution.getOutputDirectory(), outputFile);
257 if (mavenResourcesExecution.isOverwrite() || lastModified > destinationFile.lastModified()) {
258 storeProperties(outputProperties, destinationFile);
259 } else {
260 getLogger().info("Skipping merge since no files were modified");
261 }
262 }
263
264
265
266
267
268
269
270 private void handleDefaultFilterWrappers(MavenResourcesExecution mavenResourcesExecution)
271 throws MavenFilteringException {
272 List<FileUtils.FilterWrapper> filterWrappers = new ArrayList<FileUtils.FilterWrapper>();
273 if (mavenResourcesExecution.getFilterWrappers() != null) {
274 filterWrappers.addAll(mavenResourcesExecution.getFilterWrappers());
275 }
276 filterWrappers.addAll(mavenFileFilter.getDefaultFilterWrappers(mavenResourcesExecution));
277 mavenResourcesExecution.setFilterWrappers(filterWrappers);
278 }
279
280
281
282
283
284
285
286
287 private File getDestinationFile(File outputDirectory, String file) {
288 File destinationFile = new File(file);
289 if (!destinationFile.isAbsolute()) {
290 destinationFile = new File(outputDirectory, file);
291 }
292
293 if (!destinationFile.getParentFile().exists()) {
294 destinationFile.getParentFile().mkdirs();
295 }
296 return destinationFile;
297 }
298
299
300
301
302
303
304
305
306 private void setupScanner(Resource resource, Scanner scanner, boolean addDefaultExcludes) {
307 String[] includes;
308 if (resource.getIncludes() != null && !resource.getIncludes().isEmpty()) {
309 includes = resource.getIncludes().toArray(EMPTY_STRING_ARRAY);
310 } else {
311 includes = DEFAULT_INCLUDES;
312 }
313 scanner.setIncludes(includes);
314
315 String[] excludes;
316 if (resource.getExcludes() != null && !resource.getExcludes().isEmpty()) {
317 excludes = resource.getExcludes().toArray(EMPTY_STRING_ARRAY);
318 scanner.setExcludes(excludes);
319 }
320
321 if (addDefaultExcludes) {
322 scanner.addDefaultExcludes();
323 }
324
325 scanner.addDefaultExcludes();
326 }
327
328
329
330
331
332
333
334 private String getRelativeOutputDirectory(MavenResourcesExecution execution) {
335 String relOutDir = execution.getOutputDirectory().getAbsolutePath();
336
337 if (execution.getMavenProject() != null && execution.getMavenProject().getBasedir() != null) {
338 String basedir = execution.getMavenProject().getBasedir().getAbsolutePath();
339 relOutDir = PathTool.getRelativeFilePath(basedir, relOutDir);
340 if (relOutDir == null) {
341 relOutDir = execution.getOutputDirectory().getPath();
342 } else {
343 relOutDir = relOutDir.replace('\\', '/');
344 }
345 }
346
347 return relOutDir;
348 }
349
350
351
352
353
354
355
356
357 protected void storeProperties(Properties properties, File file) throws MavenFilteringException {
358 try (FileWriter f = new FileWriter(file)) {
359 TreeMap<String, Object> sortedByKeyMap = new TreeMap<>();
360 properties.entrySet().forEach(e -> sortedByKeyMap.put((String) e.getKey(), e.getValue()));
361 final Configuration configuration = new MapConfiguration(sortedByKeyMap);
362 PropertiesConfiguration p = new PropertiesConfiguration();
363 PropertiesConfigurationLayout layout = new PropertiesConfigurationLayout();
364 layout.setGlobalSeparator("=");
365 p.setLayout(layout);
366 p.copy(configuration);
367 p.write(f);
368 } catch (ConfigurationException | IOException e) {
369 throw new MavenFilteringException(e.getMessage(), e);
370 }
371 }
372
373
374
375
376
377
378
379
380
381
382
383
384
385 private void mergeProperties(Properties properties, File source, boolean filtering,
386 List<FilterWrapper> filterWrappers, String encoding, boolean overwrite) throws MavenFilteringException {
387
388 Properties p = getFilteredProperties(source, filtering, filterWrappers, encoding);
389 for (Entry<Object, Object> entry : p.entrySet()) {
390 String key = (String) entry.getKey();
391 String value = (String) entry.getValue();
392 String existing = properties.getProperty(key);
393 if (existing != null) {
394 if (overwrite) {
395 properties.setProperty(key, value);
396 getLogger().info("Overwriting existing Property '" + key + "' (existing value is '" + existing
397 + "', new value is '" + value + "') while merging source: " + source);
398 } else {
399 throw new MavenFilteringException("Property '" + key + "' already exists (existing value is '"
400 + existing + "', new value is '" + value + "') while merging source: " + source);
401 }
402 }
403 properties.setProperty(key, value);
404 }
405 }
406
407
408
409
410
411
412
413
414
415
416
417 private Properties getFilteredProperties(File source, boolean filtering, List<FilterWrapper> filterWrappers,
418 String encoding) throws MavenFilteringException {
419
420 Properties p = new Properties();
421 Reader r = null;
422 try (InputStream is = new FileInputStream(source)) {
423 r = new InputStreamReader(is, encoding);
424 if (filtering) {
425 for (FilterWrapper fw : filterWrappers) {
426 r = fw.getReader(r);
427 }
428 }
429 p.load(r);
430 } catch (IOException e) {
431 throw new MavenFilteringException(e.getMessage(), e);
432 } finally {
433 IOUtil.close(r);
434 }
435 return p;
436 }
437
438
439
440
441
442
443 public String getOutputFile() {
444 return outputFile;
445 }
446
447
448
449
450
451
452 public void setOutputFile(String outputFile) {
453 this.outputFile = outputFile;
454 }
455
456
457
458
459
460
461
462
463 public void setOverwriteProperties(boolean overwriteProperties) {
464 this.overwriteProperties = overwriteProperties;
465 }
466
467
468
469
470
471
472 public boolean isOverwriteProperties() {
473 return overwriteProperties;
474 }
475
476
477
478
479
480
481 public void setBuildContext(BuildContext buildContext) {
482 this.buildContext = buildContext;
483 }
484 }