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