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.plugins.assembly.io;
20  
21  import javax.inject.Named;
22  import javax.inject.Singleton;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.InputStreamReader;
28  import java.io.Reader;
29  import java.io.StringWriter;
30  import java.util.ArrayList;
31  import java.util.Collections;
32  import java.util.HashSet;
33  import java.util.List;
34  import java.util.Set;
35  
36  import org.apache.commons.io.input.XmlStreamReader;
37  import org.apache.maven.plugins.assembly.AssemblerConfigurationSource;
38  import org.apache.maven.plugins.assembly.InvalidAssemblerConfigurationException;
39  import org.apache.maven.plugins.assembly.interpolation.AssemblyExpressionEvaluator;
40  import org.apache.maven.plugins.assembly.interpolation.AssemblyInterpolator;
41  import org.apache.maven.plugins.assembly.model.Assembly;
42  import org.apache.maven.plugins.assembly.model.Component;
43  import org.apache.maven.plugins.assembly.model.ContainerDescriptorHandlerConfig;
44  import org.apache.maven.plugins.assembly.model.DependencySet;
45  import org.apache.maven.plugins.assembly.model.FileItem;
46  import org.apache.maven.plugins.assembly.model.FileSet;
47  import org.apache.maven.plugins.assembly.model.ModuleSet;
48  import org.apache.maven.plugins.assembly.model.io.xpp3.AssemblyXpp3Reader;
49  import org.apache.maven.plugins.assembly.model.io.xpp3.AssemblyXpp3Writer;
50  import org.apache.maven.plugins.assembly.model.io.xpp3.ComponentXpp3Reader;
51  import org.apache.maven.plugins.assembly.resolved.AssemblyId;
52  import org.apache.maven.plugins.assembly.utils.InterpolationConstants;
53  import org.apache.maven.project.MavenProject;
54  import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
55  import org.codehaus.plexus.interpolation.RecursionInterceptor;
56  import org.codehaus.plexus.interpolation.fixed.FixedStringSearchInterpolator;
57  import org.codehaus.plexus.interpolation.fixed.InterpolationState;
58  import org.codehaus.plexus.interpolation.fixed.PrefixedObjectValueSource;
59  import org.codehaus.plexus.interpolation.fixed.PrefixedPropertiesValueSource;
60  import org.codehaus.plexus.util.DirectoryScanner;
61  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
62  import org.slf4j.Logger;
63  import org.slf4j.LoggerFactory;
64  
65  /**
66   *
67   */
68  @Singleton
69  @Named
70  public class DefaultAssemblyReader implements AssemblyReader {
71      private static final Logger LOGGER = LoggerFactory.getLogger(DefaultAssemblyReader.class);
72  
73      public static FixedStringSearchInterpolator createProjectInterpolator(MavenProject project) {
74          // CHECKSTYLE_OFF: LineLength
75          return FixedStringSearchInterpolator.create(
76                  new PrefixedPropertiesValueSource(
77                          InterpolationConstants.PROJECT_PROPERTIES_PREFIXES, project.getProperties(), true),
78                  new PrefixedObjectValueSource(InterpolationConstants.PROJECT_PREFIXES, project, true));
79          // CHECKSTYLE_ON: LineLength
80      }
81  
82      @Override
83      public List<Assembly> readAssemblies(final AssemblerConfigurationSource configSource)
84              throws AssemblyReadException, InvalidAssemblerConfigurationException {
85          final Locator locator = new Locator();
86  
87          final List<LocatorStrategy> strategies = new ArrayList<>();
88          strategies.add(new RelativeFileLocatorStrategy(configSource.getBasedir()));
89          strategies.add(new FileLocatorStrategy());
90  
91          final List<LocatorStrategy> refStrategies = new ArrayList<>();
92          refStrategies.add(new PrefixedClasspathLocatorStrategy("/assemblies/"));
93  
94          final List<Assembly> assemblies = new ArrayList<>();
95  
96          final String[] descriptors = configSource.getDescriptors();
97          final String[] descriptorRefs = configSource.getDescriptorReferences();
98          final File descriptorSourceDirectory = configSource.getDescriptorSourceDirectory();
99          final List<Assembly> inlineDescriptors = configSource.getInlineDescriptors();
100 
101         if ((descriptors != null) && (descriptors.length > 0)) {
102             locator.setStrategies(strategies);
103             for (String descriptor1 : descriptors) {
104                 LOGGER.info("Reading assembly descriptor: " + descriptor1);
105                 addAssemblyFromDescriptor(descriptor1, locator, configSource, assemblies);
106             }
107         }
108 
109         if ((descriptorRefs != null) && (descriptorRefs.length > 0)) {
110             locator.setStrategies(refStrategies);
111             for (String descriptorRef : descriptorRefs) {
112                 addAssemblyForDescriptorReference(descriptorRef, configSource, assemblies);
113             }
114         }
115 
116         if ((descriptorSourceDirectory != null) && descriptorSourceDirectory.isDirectory()) {
117             locator.setStrategies(
118                     Collections.singletonList(new RelativeFileLocatorStrategy(descriptorSourceDirectory)));
119 
120             final DirectoryScanner scanner = new DirectoryScanner();
121             scanner.setBasedir(descriptorSourceDirectory);
122             scanner.setIncludes(new String[] {"**/*.xml"});
123             scanner.addDefaultExcludes();
124 
125             scanner.scan();
126 
127             final String[] paths = scanner.getIncludedFiles();
128 
129             for (String path : paths) {
130                 addAssemblyFromDescriptor(path, locator, configSource, assemblies);
131             }
132         }
133 
134         if (inlineDescriptors != null) {
135             assemblies.addAll(inlineDescriptors);
136         }
137 
138         if (assemblies.isEmpty()) {
139             if (configSource.isIgnoreMissingDescriptor()) {
140                 LOGGER.debug("Ignoring missing assembly descriptors per configuration. "
141                         + "See messages above for specifics.");
142             } else {
143                 throw new AssemblyReadException("No assembly descriptors found.");
144             }
145         }
146 
147         // check unique IDs
148         final Set<String> ids = new HashSet<>();
149         for (final Assembly assembly : assemblies) {
150             if (!ids.add(assembly.getId())) {
151                 LOGGER.warn("The assembly id " + assembly.getId() + " is used more than once.");
152             }
153         }
154         return assemblies;
155     }
156 
157     @Override
158     public Assembly getAssemblyForDescriptorReference(final String ref, final AssemblerConfigurationSource configSource)
159             throws AssemblyReadException, InvalidAssemblerConfigurationException {
160         return addAssemblyForDescriptorReference(ref, configSource, new ArrayList<>(1));
161     }
162 
163     @Override
164     public Assembly getAssemblyFromDescriptorFile(final File file, final AssemblerConfigurationSource configSource)
165             throws AssemblyReadException, InvalidAssemblerConfigurationException {
166         return addAssemblyFromDescriptorFile(file, configSource, new ArrayList<>(1));
167     }
168 
169     private Assembly addAssemblyForDescriptorReference(
170             final String ref, final AssemblerConfigurationSource configSource, final List<Assembly> assemblies)
171             throws AssemblyReadException, InvalidAssemblerConfigurationException {
172         final InputStream resourceAsStream = getClass().getResourceAsStream("/assemblies/" + ref + ".xml");
173 
174         if (resourceAsStream == null) {
175             if (configSource.isIgnoreMissingDescriptor()) {
176                 LOGGER.debug("Ignoring missing assembly descriptor with ID '" + ref + "' per configuration.");
177                 return null;
178             } else {
179                 throw new AssemblyReadException("Descriptor with ID '" + ref + "' not found");
180             }
181         }
182 
183         try (Reader reader =
184                 XmlStreamReader.builder().setInputStream(resourceAsStream).get()) {
185             final Assembly assembly = readAssembly(reader, ref, null, configSource);
186             assemblies.add(assembly);
187             return assembly;
188         } catch (final IOException e) {
189             throw new AssemblyReadException("Problem with descriptor with ID '" + ref + "'", e);
190         }
191     }
192 
193     private Assembly addAssemblyFromDescriptorFile(
194             final File descriptor, final AssemblerConfigurationSource configSource, final List<Assembly> assemblies)
195             throws AssemblyReadException, InvalidAssemblerConfigurationException {
196         if (!descriptor.exists()) {
197             if (configSource.isIgnoreMissingDescriptor()) {
198                 LOGGER.debug("Ignoring missing assembly descriptor: '" + descriptor + "' per configuration.");
199                 return null;
200             } else {
201                 throw new AssemblyReadException("Descriptor: '" + descriptor + "' not found");
202             }
203         }
204 
205         try (Reader r = XmlStreamReader.builder().setFile(descriptor).get()) {
206             final Assembly assembly =
207                     readAssembly(r, descriptor.getAbsolutePath(), descriptor.getParentFile(), configSource);
208 
209             assemblies.add(assembly);
210 
211             return assembly;
212         } catch (final IOException e) {
213             throw new AssemblyReadException("Error reading assembly descriptor: " + descriptor, e);
214         }
215     }
216 
217     private Assembly addAssemblyFromDescriptor(
218             final String spec,
219             final Locator locator,
220             final AssemblerConfigurationSource configSource,
221             final List<Assembly> assemblies)
222             throws AssemblyReadException, InvalidAssemblerConfigurationException {
223         final Location location = locator.resolve(spec);
224 
225         if (location == null) {
226             if (configSource.isIgnoreMissingDescriptor()) {
227                 LOGGER.debug("Ignoring missing assembly descriptor with ID '" + spec
228                         + "' per configuration.\nLocator output was:\n\n"
229                         + locator.getMessageHolder().render());
230                 return null;
231             } else {
232                 throw new AssemblyReadException("Error locating assembly descriptor: " + spec + "\n\n"
233                         + locator.getMessageHolder().render());
234             }
235         }
236 
237         try (Reader r = XmlStreamReader.builder()
238                 .setInputStream(location.getInputStream())
239                 .get()) {
240             File dir = null;
241             if (location.getFile() != null) {
242                 dir = location.getFile().getParentFile();
243             }
244 
245             final Assembly assembly = readAssembly(r, spec, dir, configSource);
246 
247             assemblies.add(assembly);
248 
249             return assembly;
250         } catch (final IOException e) {
251             throw new AssemblyReadException("Error reading assembly descriptor: " + spec, e);
252         }
253     }
254 
255     public Assembly readAssembly(
256             Reader reader,
257             final String locationDescription,
258             final File assemblyDir,
259             final AssemblerConfigurationSource configSource)
260             throws AssemblyReadException, InvalidAssemblerConfigurationException {
261         Assembly assembly;
262 
263         final MavenProject project = configSource.getProject();
264         try {
265 
266             InterpolationState is = new InterpolationState();
267             final RecursionInterceptor interceptor =
268                     new PrefixAwareRecursionInterceptor(InterpolationConstants.PROJECT_PREFIXES, true);
269             is.setRecursionInterceptor(interceptor);
270 
271             FixedStringSearchInterpolator interpolator =
272                     AssemblyInterpolator.fullInterpolator(project, createProjectInterpolator(project), configSource);
273             AssemblyXpp3Reader.ContentTransformer transformer =
274                     AssemblyInterpolator.assemblyInterpolator(interpolator, is, LOGGER);
275 
276             final AssemblyXpp3Reader r = new AssemblyXpp3Reader(transformer);
277             assembly = r.read(reader);
278 
279             ComponentXpp3Reader.ContentTransformer ctrans =
280                     AssemblyInterpolator.componentInterpolator(interpolator, is, LOGGER);
281             mergeComponentsWithMainAssembly(assembly, assemblyDir, configSource, ctrans);
282             debugPrintAssembly("After assembly is interpolated:", assembly);
283 
284             AssemblyInterpolator.checkErrors(AssemblyId.createAssemblyId(assembly), is, LOGGER);
285 
286         } catch (final IOException | XmlPullParserException e) {
287             throw new AssemblyReadException(
288                     "Error reading descriptor: " + locationDescription + ": " + e.getMessage(), e);
289         }
290 
291         if (assembly.isIncludeSiteDirectory()) {
292             includeSiteInAssembly(assembly, configSource);
293         }
294 
295         return assembly;
296     }
297 
298     private void debugPrintAssembly(final String message, final Assembly assembly) {
299         final StringWriter sWriter = new StringWriter();
300         try {
301             new AssemblyXpp3Writer().write(sWriter, assembly);
302         } catch (final IOException e) {
303             LOGGER.debug("Failed to print debug message with assembly descriptor listing, and message: " + message, e);
304         }
305 
306         LOGGER.debug(message + "\n\n" + sWriter + "\n\n");
307     }
308 
309     /**
310      * Add the contents of all included components to main assembly
311      *
312      * @param assembly the assembly
313      * @param assemblyDir the assembly directory
314      * @param transformer the component interpolator
315      * @throws AssemblyReadException
316      */
317     protected void mergeComponentsWithMainAssembly(
318             final Assembly assembly,
319             final File assemblyDir,
320             final AssemblerConfigurationSource configSource,
321             ComponentXpp3Reader.ContentTransformer transformer)
322             throws AssemblyReadException {
323         final Locator locator = new Locator();
324 
325         if (assemblyDir != null && assemblyDir.exists() && assemblyDir.isDirectory()) {
326             locator.addStrategy(new RelativeFileLocatorStrategy(assemblyDir));
327         }
328 
329         // allow absolute paths in componentDescriptor... MASSEMBLY-486
330         locator.addStrategy(new RelativeFileLocatorStrategy(configSource.getBasedir()));
331         locator.addStrategy(new FileLocatorStrategy());
332         locator.addStrategy(new ClasspathResourceLocatorStrategy());
333 
334         final AssemblyExpressionEvaluator aee = new AssemblyExpressionEvaluator(configSource);
335 
336         final List<String> componentLocations = assembly.getComponentDescriptors();
337 
338         for (String location : componentLocations) {
339             // allow expressions in path to component descriptor... MASSEMBLY-486
340             try {
341                 location = aee.evaluate(location).toString();
342             } catch (final Exception eee) {
343                 LOGGER.error("Error interpolating componentDescriptor: " + location, eee);
344             }
345 
346             final Location resolvedLocation = locator.resolve(location);
347 
348             if (resolvedLocation == null) {
349                 throw new AssemblyReadException("Failed to locate component descriptor: " + location);
350             }
351 
352             Component component = null;
353             try (Reader reader = new InputStreamReader(resolvedLocation.getInputStream())) {
354                 component = new ComponentXpp3Reader(transformer).read(reader);
355             } catch (final IOException | XmlPullParserException e) {
356                 throw new AssemblyReadException(
357                         "Error reading component descriptor: " + location + " (resolved to: "
358                                 + resolvedLocation.getSpecification() + ")",
359                         e);
360             }
361 
362             mergeComponentWithAssembly(component, assembly);
363         }
364     }
365 
366     /**
367      * Add the content of a single Component to main assembly
368      *
369      * @param component The component
370      * @param assembly The assembly
371      */
372     protected void mergeComponentWithAssembly(final Component component, final Assembly assembly) {
373         final List<ContainerDescriptorHandlerConfig> containerHandlerDescriptors =
374                 component.getContainerDescriptorHandlers();
375 
376         for (final ContainerDescriptorHandlerConfig cfg : containerHandlerDescriptors) {
377             assembly.addContainerDescriptorHandler(cfg);
378         }
379 
380         final List<DependencySet> dependencySetList = component.getDependencySets();
381 
382         for (final DependencySet dependencySet : dependencySetList) {
383             assembly.addDependencySet(dependencySet);
384         }
385 
386         final List<FileSet> fileSetList = component.getFileSets();
387 
388         for (final FileSet fileSet : fileSetList) {
389             assembly.addFileSet(fileSet);
390         }
391 
392         final List<FileItem> fileList = component.getFiles();
393 
394         for (final FileItem fileItem : fileList) {
395             assembly.addFile(fileItem);
396         }
397 
398         final List<ModuleSet> moduleSets = component.getModuleSets();
399         for (final ModuleSet moduleSet : moduleSets) {
400             assembly.addModuleSet(moduleSet);
401         }
402     }
403 
404     @Override
405     public void includeSiteInAssembly(final Assembly assembly, final AssemblerConfigurationSource configSource)
406             throws InvalidAssemblerConfigurationException {
407         final File siteDirectory = configSource.getSiteDirectory();
408 
409         if (!siteDirectory.exists()) {
410             throw new InvalidAssemblerConfigurationException("site did not exist in the target directory - "
411                     + "please run site:site before creating the assembly");
412         }
413 
414         LOGGER.info("Adding site directory to assembly : " + siteDirectory);
415 
416         final FileSet siteFileSet = new FileSet();
417 
418         siteFileSet.setDirectory(siteDirectory.getPath());
419 
420         siteFileSet.setOutputDirectory("/site");
421 
422         assembly.addFileSet(siteFileSet);
423     }
424 }