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