View Javadoc
1   package org.apache.maven.plugin.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 java.io.File;
23  import java.io.FileInputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.InputStreamReader;
27  import java.io.Reader;
28  import java.io.StringWriter;
29  import java.io.UnsupportedEncodingException;
30  import java.util.ArrayList;
31  import java.util.Collections;
32  import java.util.Enumeration;
33  import java.util.HashMap;
34  import java.util.HashSet;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.Properties;
38  import java.util.Set;
39  
40  import org.apache.maven.execution.MavenSession;
41  import org.apache.maven.plugin.assembly.AssemblerConfigurationSource;
42  import org.apache.maven.plugin.assembly.InvalidAssemblerConfigurationException;
43  import org.apache.maven.plugin.assembly.interpolation.AssemblyExpressionEvaluator;
44  import org.apache.maven.plugin.assembly.interpolation.AssemblyInterpolationException;
45  import org.apache.maven.plugin.assembly.interpolation.AssemblyInterpolator;
46  import org.apache.maven.plugin.assembly.model.Assembly;
47  import org.apache.maven.plugin.assembly.model.Component;
48  import org.apache.maven.plugin.assembly.model.ContainerDescriptorHandlerConfig;
49  import org.apache.maven.plugin.assembly.model.DependencySet;
50  import org.apache.maven.plugin.assembly.model.FileItem;
51  import org.apache.maven.plugin.assembly.model.FileSet;
52  import org.apache.maven.plugin.assembly.model.ModuleSet;
53  import org.apache.maven.plugin.assembly.model.Repository;
54  import org.apache.maven.plugin.assembly.model.io.xpp3.AssemblyXpp3Reader;
55  import org.apache.maven.plugin.assembly.model.io.xpp3.AssemblyXpp3Writer;
56  import org.apache.maven.plugin.assembly.model.io.xpp3.ComponentXpp3Reader;
57  import org.apache.maven.project.MavenProject;
58  import org.apache.maven.shared.io.location.ClasspathResourceLocatorStrategy;
59  import org.apache.maven.shared.io.location.FileLocatorStrategy;
60  import org.apache.maven.shared.io.location.Location;
61  import org.apache.maven.shared.io.location.Locator;
62  import org.apache.maven.shared.io.location.LocatorStrategy;
63  import org.codehaus.plexus.logging.AbstractLogEnabled;
64  import org.codehaus.plexus.logging.Logger;
65  import org.codehaus.plexus.logging.console.ConsoleLogger;
66  import org.codehaus.plexus.util.DirectoryScanner;
67  import org.codehaus.plexus.util.IOUtil;
68  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
69  
70  /**
71   * @version $Id: DefaultAssemblyReader.java 1601216 2014-06-08 11:58:09Z khmarbaise $
72   */
73  @org.codehaus.plexus.component.annotations.Component( role = AssemblyReader.class )
74  public class DefaultAssemblyReader
75      extends AbstractLogEnabled
76      implements AssemblyReader
77  {
78  
79      public List<Assembly> readAssemblies( final AssemblerConfigurationSource configSource )
80          throws AssemblyReadException, InvalidAssemblerConfigurationException
81      {
82          final Locator locator = new Locator();
83  
84          final List<LocatorStrategy> strategies = new ArrayList<LocatorStrategy>();
85          strategies.add( new RelativeFileLocatorStrategy( configSource.getBasedir() ) );
86          strategies.add( new FileLocatorStrategy() );
87  
88          final List<LocatorStrategy> refStrategies = new ArrayList<LocatorStrategy>();
89          refStrategies.add( new PrefixedClasspathLocatorStrategy( "/assemblies/" ) );
90  
91          final List<Assembly> assemblies = new ArrayList<Assembly>();
92  
93          final String descriptor = configSource.getDescriptor();
94          final String descriptorId = configSource.getDescriptorId();
95          final String[] descriptors = configSource.getDescriptors();
96          final String[] descriptorRefs = configSource.getDescriptorReferences();
97          final File descriptorSourceDirectory = configSource.getDescriptorSourceDirectory();
98  
99          if ( descriptor != null )
100         {
101             locator.setStrategies( strategies );
102             addAssemblyFromDescriptor( descriptor, locator, configSource, assemblies );
103         }
104 
105         if ( descriptorId != null )
106         {
107             locator.setStrategies( refStrategies );
108             addAssemblyForDescriptorReference( descriptorId, configSource, assemblies );
109         }
110 
111         if ( ( descriptors != null ) && ( descriptors.length > 0 ) )
112         {
113             locator.setStrategies( strategies );
114             for (String descriptor1 : descriptors) {
115                 getLogger().info("Reading assembly descriptor: " + descriptor1);
116                 addAssemblyFromDescriptor(descriptor1, locator, configSource, assemblies);
117             }
118         }
119 
120         if ( ( descriptorRefs != null ) && ( descriptorRefs.length > 0 ) )
121         {
122             locator.setStrategies( refStrategies );
123             for (String descriptorRef : descriptorRefs) {
124                 addAssemblyForDescriptorReference(descriptorRef, configSource, assemblies);
125             }
126         }
127 
128         if ( ( descriptorSourceDirectory != null ) && descriptorSourceDirectory.isDirectory() )
129         {
130             locator.setStrategies( Collections.singletonList( new RelativeFileLocatorStrategy(
131                                                                                                descriptorSourceDirectory ) ) );
132 
133             final DirectoryScanner scanner = new DirectoryScanner();
134             scanner.setBasedir( descriptorSourceDirectory );
135             scanner.setIncludes( new String[] { "**/*.xml" } );
136             scanner.addDefaultExcludes();
137 
138             try
139             {
140                 scanner.scan();
141             }
142             // FIXME: plexus-utils >= 1.3-SNAPSHOT should fix this.
143             catch ( final NullPointerException e )
144             {
145                 final StackTraceElement frameZero = e.getStackTrace()[0];
146 
147                 if ( "org.codehaus.plexus.util.DirectoryScanner".equals( frameZero.getClassName() )
148                     && "scandir".equals( frameZero.getMethodName() ) )
149                 {
150                     if ( getLogger().isDebugEnabled() )
151                     {
152                         getLogger().debug( "Caught filesystem error while scanning directories..."
153                                                + "using zero-length list as the result.", e );
154                     }
155                 }
156                 else
157                 {
158                     throw e;
159                 }
160             }
161 
162             final String[] paths = scanner.getIncludedFiles();
163 
164             if ( paths != null )
165             {
166                 for (String path : paths) {
167                     addAssemblyFromDescriptor(path, locator, configSource, assemblies);
168                 }
169             }
170         }
171 
172         if ( assemblies.isEmpty() )
173         {
174             if ( configSource.isIgnoreMissingDescriptor() )
175             {
176                 getLogger().debug( "Ignoring missing assembly descriptors per configuration. See messages above for specifics." );
177             }
178             else
179             {
180                 throw new AssemblyReadException( "No assembly descriptors found." );
181             }
182         }
183 
184         // check unique IDs
185         final Set<String> ids = new HashSet<String>();
186         for (final Assembly assembly : assemblies) {
187             if (!ids.add(assembly.getId())) {
188                 getLogger().warn("The assembly id " + assembly.getId() + " is used more than once.");
189             }
190 
191         }
192         return assemblies;
193     }
194 
195     public Assembly getAssemblyForDescriptorReference( final String ref, final AssemblerConfigurationSource configSource )
196         throws AssemblyReadException, InvalidAssemblerConfigurationException
197     {
198         return addAssemblyForDescriptorReference( ref, configSource, new ArrayList<Assembly>( 1 ) );
199     }
200 
201     public Assembly getAssemblyFromDescriptorFile( final File file, final AssemblerConfigurationSource configSource )
202         throws AssemblyReadException, InvalidAssemblerConfigurationException
203     {
204         return addAssemblyFromDescriptorFile( file, configSource, new ArrayList<Assembly>( 1 ) );
205     }
206 
207     private Assembly addAssemblyForDescriptorReference( final String ref,
208                                                         final AssemblerConfigurationSource configSource,
209                                                         final List<Assembly> assemblies )
210         throws AssemblyReadException, InvalidAssemblerConfigurationException
211     {
212         final InputStream resourceAsStream =
213             Thread.currentThread().getContextClassLoader().getResourceAsStream( "assemblies/" + ref + ".xml" );
214 
215         if ( resourceAsStream == null )
216         {
217             if ( configSource.isIgnoreMissingDescriptor() )
218             {
219                 getLogger().debug( "Ignoring missing assembly descriptor with ID '" + ref + "' per configuration." );
220                 return null;
221             }
222             else
223             {
224                 throw new AssemblyReadException( "Descriptor with ID '" + ref + "' not found" );
225             }
226         }
227 
228         try
229         {
230             // TODO use ReaderFactory.newXmlReader() when plexus-utils is upgraded to 1.4.5+
231             final Assembly assembly =
232                 readAssembly( new InputStreamReader( resourceAsStream, "UTF-8" ), ref, null, configSource );
233 
234             assemblies.add( assembly );
235             return assembly;
236         }
237         catch ( final UnsupportedEncodingException e )
238         {
239             // should not occur since UTF-8 support is mandatory
240             throw new AssemblyReadException( "Encoding not supported for descriptor with ID '" + ref + "'" );
241         }
242     }
243 
244     private Assembly addAssemblyFromDescriptorFile( final File descriptor,
245                                                     final AssemblerConfigurationSource configSource,
246                                                     final List<Assembly> assemblies )
247         throws AssemblyReadException, InvalidAssemblerConfigurationException
248     {
249         if ( !descriptor.exists() )
250         {
251             if ( configSource.isIgnoreMissingDescriptor() )
252             {
253                 getLogger().debug( "Ignoring missing assembly descriptor: '" + descriptor + "' per configuration." );
254                 return null;
255             }
256             else
257             {
258                 throw new AssemblyReadException( "Descriptor: '" + descriptor + "' not found" );
259             }
260         }
261 
262         Reader r = null;
263         try
264         {
265             // TODO use ReaderFactory.newXmlReader() when plexus-utils is upgraded to 1.4.5+
266             r = new InputStreamReader( new FileInputStream( descriptor ), "UTF-8" );
267             final Assembly assembly =
268                 readAssembly( r, descriptor.getAbsolutePath(), descriptor.getParentFile(), configSource );
269 
270             assemblies.add( assembly );
271 
272             return assembly;
273         }
274         catch ( final IOException e )
275         {
276             throw new AssemblyReadException( "Error reading assembly descriptor: " + descriptor, e );
277         }
278         finally
279         {
280             IOUtil.close( r );
281         }
282     }
283 
284     private Assembly addAssemblyFromDescriptor( final String spec, final Locator locator,
285                                                 final AssemblerConfigurationSource configSource,
286                                                 final List<Assembly> assemblies )
287         throws AssemblyReadException, InvalidAssemblerConfigurationException
288     {
289         final Location location = locator.resolve( spec );
290 
291         if ( location == null )
292         {
293             if ( configSource.isIgnoreMissingDescriptor() )
294             {
295                 getLogger().debug( "Ignoring missing assembly descriptor with ID '" + spec
296                                        + "' per configuration.\nLocator output was:\n\n"
297                                        + locator.getMessageHolder().render() );
298                 return null;
299             }
300             else
301             {
302                 throw new AssemblyReadException( "Error locating assembly descriptor: " + spec + "\n\n"
303                     + locator.getMessageHolder().render() );
304             }
305         }
306 
307         Reader r = null;
308         try
309         {
310             // TODO use ReaderFactory.newXmlReader() when plexus-utils is upgraded to 1.4.5+
311             r = new InputStreamReader( location.getInputStream(), "UTF-8" );
312 
313             File dir = null;
314             if ( location.getFile() != null )
315             {
316                 dir = location.getFile().getParentFile();
317             }
318 
319             final Assembly assembly = readAssembly( r, spec, dir, configSource );
320 
321             assemblies.add( assembly );
322 
323             return assembly;
324         }
325         catch ( final IOException e )
326         {
327             throw new AssemblyReadException( "Error reading assembly descriptor: " + spec, e );
328         }
329         finally
330         {
331             IOUtil.close( r );
332         }
333 
334     }
335 
336     protected Assembly readAssembly( final Reader reader, final String locationDescription, final File assemblyDir,
337                                      final AssemblerConfigurationSource configSource )
338         throws AssemblyReadException, InvalidAssemblerConfigurationException
339     {
340         Assembly assembly;
341 
342         final File basedir = configSource.getBasedir();
343         final MavenProject project = configSource.getProject();
344 
345         try
346         {
347             final Map<String, String> context = new HashMap<String, String>();
348             final MavenSession session = configSource.getMavenSession();
349 
350             Properties commandLineProperties = System.getProperties();
351             if ( session != null )
352             {
353                 commandLineProperties = new Properties();
354                 if ( session.getExecutionProperties() != null )
355                 {
356                     commandLineProperties.putAll( session.getExecutionProperties() );
357                 }
358 
359                 if ( session.getUserProperties() != null )
360                 {
361                     commandLineProperties.putAll( session.getUserProperties() );
362                 }
363             }
364 
365             for ( final Enumeration<Object> e = commandLineProperties.keys(); e.hasMoreElements(); )
366             {
367                 final String key = (String) e.nextElement();
368                 if ( key == null || key.trim().length() < 1 )
369                 {
370                     continue;
371                 }
372 
373                 context.put( key, commandLineProperties.getProperty( key ) );
374             }
375 
376             context.put( "basedir", basedir.getAbsolutePath() );
377 
378             final AssemblyXpp3Reader r = new AssemblyXpp3Reader();
379             assembly = r.read( reader );
380 
381             mergeComponentsWithMainAssembly( assembly, assemblyDir, configSource );
382 
383             debugPrintAssembly( "Before assembly is interpolated:", assembly );
384 
385             assembly = new AssemblyInterpolator().interpolate( assembly, project, configSource );
386 
387             debugPrintAssembly( "After assembly is interpolated:", assembly );
388         }
389         catch ( final IOException e )
390         {
391             throw new AssemblyReadException(
392                                              "Error reading descriptor: " + locationDescription + ": " + e.getMessage(),
393                                              e );
394         }
395         catch ( final XmlPullParserException e )
396         {
397             throw new AssemblyReadException(
398                                              "Error reading descriptor: " + locationDescription + ": " + e.getMessage(),
399                                              e );
400         }
401         catch ( final AssemblyInterpolationException e )
402         {
403             throw new AssemblyReadException(
404                                              "Error reading descriptor: " + locationDescription + ": " + e.getMessage(),
405                                              e );
406         }
407         finally
408         {
409             IOUtil.close( reader );
410         }
411 
412         if ( configSource.isSiteIncluded() || assembly.isIncludeSiteDirectory() )
413         {
414             includeSiteInAssembly( assembly, configSource );
415         }
416 
417         return assembly;
418     }
419 
420     private void debugPrintAssembly( final String message, final Assembly assembly )
421     {
422         final StringWriter sWriter = new StringWriter();
423         try
424         {
425             new AssemblyXpp3Writer().write( sWriter, assembly );
426         }
427         catch ( final IOException e )
428         {
429             getLogger().debug( "Failed to print debug message with assembly descriptor listing, and message: "
430                                    + message, e );
431         }
432 
433         getLogger().debug( message + "\n\n" + sWriter.toString() + "\n\n" );
434     }
435 
436     /**
437      * Add the contents of all included components to main assembly
438      * 
439      * @param assembly
440      * @param assemblyDir
441      * @throws AssemblyReadException
442      */
443     protected void mergeComponentsWithMainAssembly( final Assembly assembly, final File assemblyDir,
444                                                     final AssemblerConfigurationSource configSource )
445         throws AssemblyReadException
446     {
447         final Locator locator = new Locator();
448 
449         if ( assemblyDir != null && assemblyDir.exists() && assemblyDir.isDirectory() )
450         {
451             locator.addStrategy( new RelativeFileLocatorStrategy( assemblyDir ) );
452         }
453 
454         // allow absolute paths in componentDescriptor... MASSEMBLY-486
455         locator.addStrategy( new RelativeFileLocatorStrategy( configSource.getBasedir() ) );
456         locator.addStrategy( new FileLocatorStrategy() );
457         locator.addStrategy( new ClasspathResourceLocatorStrategy() );
458 
459         final AssemblyExpressionEvaluator aee = new AssemblyExpressionEvaluator( configSource );
460 
461         final List<String> componentLocations = assembly.getComponentDescriptors();
462 
463         for (String location : componentLocations) {
464             // allow expressions in path to component descriptor... MASSEMBLY-486
465             try {
466                 location = aee.evaluate(location).toString();
467             } catch (final Exception eee) {
468                 getLogger().error("Error interpolating componentDescriptor: " + location, eee);
469             }
470 
471             final Location resolvedLocation = locator.resolve(location);
472 
473             if (resolvedLocation == null) {
474                 throw new AssemblyReadException("Failed to locate component descriptor: " + location);
475             }
476 
477             Component component = null;
478             Reader reader = null;
479             try {
480                 reader = new InputStreamReader(resolvedLocation.getInputStream());
481                 component = new ComponentXpp3Reader().read(reader);
482             } catch (final IOException e) {
483                 throw new AssemblyReadException("Error reading component descriptor: " + location + " (resolved to: "
484                         + resolvedLocation.getSpecification() + ")", e);
485             } catch (final XmlPullParserException e) {
486                 throw new AssemblyReadException("Error reading component descriptor: " + location + " (resolved to: "
487                         + resolvedLocation.getSpecification() + ")", e);
488             } finally {
489                 IOUtil.close(reader);
490             }
491 
492             mergeComponentWithAssembly(component, assembly);
493         }
494     }
495 
496     /**
497      * Add the content of a single Component to main assembly
498      * 
499      * @param component
500      * @param assembly
501      */
502     protected void mergeComponentWithAssembly( final Component component, final Assembly assembly )
503     {
504         final List<ContainerDescriptorHandlerConfig> containerHandlerDescriptors =
505             component.getContainerDescriptorHandlers();
506 
507         for (final ContainerDescriptorHandlerConfig cfg : containerHandlerDescriptors) {
508             assembly.addContainerDescriptorHandler(cfg);
509         }
510 
511         final List<DependencySet> dependencySetList = component.getDependencySets();
512 
513         for (final DependencySet dependencySet : dependencySetList) {
514             assembly.addDependencySet(dependencySet);
515         }
516 
517         final List<FileSet> fileSetList = component.getFileSets();
518 
519         for (final FileSet fileSet : fileSetList) {
520             assembly.addFileSet(fileSet);
521         }
522 
523         final List<FileItem> fileList = component.getFiles();
524 
525         for (final FileItem fileItem : fileList) {
526             assembly.addFile(fileItem);
527         }
528 
529         final List<Repository> repositoriesList = component.getRepositories();
530 
531         for (final Repository repository : repositoriesList) {
532             assembly.addRepository(repository);
533         }
534 
535         final List<ModuleSet> moduleSets = component.getModuleSets();
536         for ( final ModuleSet moduleSet : moduleSets )
537         {
538             assembly.addModuleSet( moduleSet );
539         }
540     }
541 
542     public void includeSiteInAssembly( final Assembly assembly, final AssemblerConfigurationSource configSource )
543         throws InvalidAssemblerConfigurationException
544     {
545         final File siteDirectory = configSource.getSiteDirectory();
546 
547         if ( !siteDirectory.exists() )
548         {
549             throw new InvalidAssemblerConfigurationException(
550                                                               "site did not exist in the target directory - please run site:site before creating the assembly" );
551         }
552 
553         getLogger().info( "Adding site directory to assembly : " + siteDirectory );
554 
555         final FileSet siteFileSet = new FileSet();
556 
557         siteFileSet.setDirectory( siteDirectory.getPath() );
558 
559         siteFileSet.setOutputDirectory( "/site" );
560 
561         assembly.addFileSet( siteFileSet );
562     }
563 
564     @Override
565     protected Logger getLogger()
566     {
567         Logger logger = super.getLogger();
568 
569         if ( logger == null )
570         {
571             logger = new ConsoleLogger( Logger.LEVEL_INFO, "assemblyReader-internal" );
572             enableLogging( logger );
573         }
574 
575         return logger;
576     }
577 
578 }