View Javadoc
1   /*
2    *   Copyright (C) 2005 Christian Schulte <cs@schulte.it>
3    *   All rights reserved.
4    *
5    *   Redistribution and use in source and binary forms, with or without
6    *   modification, are permitted provided that the following conditions
7    *   are met:
8    *
9    *     o Redistributions of source code must retain the above copyright
10   *       notice, this list of conditions and the following disclaimer.
11   *
12   *     o Redistributions in binary form must reproduce the above copyright
13   *       notice, this list of conditions and the following disclaimer in
14   *       the documentation and/or other materials provided with the
15   *       distribution.
16   *
17   *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
18   *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
19   *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
20   *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
21   *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22   *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23   *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24   *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25   *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26   *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27   *
28   *   $JOMC: MergeModulesTask.java 5263 2016-05-01 23:44:12Z schulte $
29   *
30   */
31  package org.jomc.ant;
32  
33  import java.io.ByteArrayOutputStream;
34  import java.io.File;
35  import java.io.IOException;
36  import java.io.InputStream;
37  import java.io.OutputStreamWriter;
38  import java.net.HttpURLConnection;
39  import java.net.SocketTimeoutException;
40  import java.net.URISyntaxException;
41  import java.net.URL;
42  import java.net.URLConnection;
43  import java.util.ArrayList;
44  import java.util.HashSet;
45  import java.util.Iterator;
46  import java.util.LinkedList;
47  import java.util.List;
48  import java.util.Set;
49  import java.util.logging.Level;
50  import javax.xml.bind.JAXBElement;
51  import javax.xml.bind.JAXBException;
52  import javax.xml.bind.Marshaller;
53  import javax.xml.bind.Unmarshaller;
54  import javax.xml.bind.util.JAXBResult;
55  import javax.xml.bind.util.JAXBSource;
56  import javax.xml.transform.Source;
57  import javax.xml.transform.Transformer;
58  import javax.xml.transform.TransformerConfigurationException;
59  import javax.xml.transform.TransformerException;
60  import javax.xml.transform.stream.StreamSource;
61  import org.apache.tools.ant.BuildException;
62  import org.apache.tools.ant.Project;
63  import org.jomc.ant.types.NameType;
64  import org.jomc.ant.types.ResourceType;
65  import org.jomc.ant.types.TransformerResourceType;
66  import org.jomc.model.Module;
67  import org.jomc.model.Modules;
68  import org.jomc.model.ObjectFactory;
69  import org.jomc.model.modlet.DefaultModelProvider;
70  import org.jomc.modlet.ModelContext;
71  import org.jomc.modlet.ModelException;
72  import org.jomc.modlet.ModelValidationReport;
73  
74  /**
75   * Task for merging module resources.
76   *
77   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
78   * @version $JOMC: MergeModulesTask.java 5263 2016-05-01 23:44:12Z schulte $
79   */
80  public final class MergeModulesTask extends JomcModelTask
81  {
82  
83      /**
84       * The encoding of the module resource.
85       */
86      private String moduleEncoding;
87  
88      /**
89       * File to write the merged module to.
90       */
91      private File moduleFile;
92  
93      /**
94       * The name of the merged module.
95       */
96      private String moduleName;
97  
98      /**
99       * The version of the merged module.
100      */
101     private String moduleVersion;
102 
103     /**
104      * The vendor of the merged module.
105      */
106     private String moduleVendor;
107 
108     /**
109      * Included modules.
110      */
111     private Set<NameType> moduleIncludes;
112 
113     /**
114      * Excluded modules.
115      */
116     private Set<NameType> moduleExcludes;
117 
118     /**
119      * XSLT documents to use for transforming model objects.
120      */
121     private List<TransformerResourceType> modelObjectStylesheetResources;
122 
123     /**
124      * Creates a new {@code MergeModulesTask} instance.
125      */
126     public MergeModulesTask()
127     {
128         super();
129     }
130 
131     /**
132      * Gets the file to write the merged module to.
133      *
134      * @return The file to write the merged module to or {@code null}.
135      *
136      * @see #setModuleFile(java.io.File)
137      */
138     public File getModuleFile()
139     {
140         return this.moduleFile;
141     }
142 
143     /**
144      * Sets the file to write the merged module to.
145      *
146      * @param value The new file to write the merged module to or {@code null}.
147      *
148      * @see #getModuleFile()
149      */
150     public void setModuleFile( final File value )
151     {
152         this.moduleFile = value;
153     }
154 
155     /**
156      * Gets the encoding of the module resource.
157      *
158      * @return The encoding of the module resource.
159      *
160      * @see #setModuleEncoding(java.lang.String)
161      */
162     public String getModuleEncoding()
163     {
164         if ( this.moduleEncoding == null )
165         {
166             this.moduleEncoding = new OutputStreamWriter( new ByteArrayOutputStream() ).getEncoding();
167         }
168 
169         return this.moduleEncoding;
170     }
171 
172     /**
173      * Sets the encoding of the module resource.
174      *
175      * @param value The new encoding of the module resource or {@code null}.
176      *
177      * @see #getModuleEncoding()
178      */
179     public void setModuleEncoding( final String value )
180     {
181         this.moduleEncoding = value;
182     }
183 
184     /**
185      * Gets the name of the merged module.
186      *
187      * @return The name of the merged module or {@code null}.
188      *
189      * @see #setModuleName(java.lang.String)
190      */
191     public String getModuleName()
192     {
193         return this.moduleName;
194     }
195 
196     /**
197      * Sets the name of the merged module.
198      *
199      * @param value The new name of the merged module or {@code null}.
200      *
201      * @see #getModuleName()
202      */
203     public void setModuleName( final String value )
204     {
205         this.moduleName = value;
206     }
207 
208     /**
209      * Gets the version of the merged module.
210      *
211      * @return The version of the merged module or {@code null}.
212      *
213      * @see #setModuleVersion(java.lang.String)
214      */
215     public String getModuleVersion()
216     {
217         return this.moduleVersion;
218     }
219 
220     /**
221      * Sets the version of the merged module.
222      *
223      * @param value The new version of the merged module or {@code null}.
224      *
225      * @see #getModuleVersion()
226      */
227     public void setModuleVersion( final String value )
228     {
229         this.moduleVersion = value;
230     }
231 
232     /**
233      * Gets the vendor of the merged module.
234      *
235      * @return The vendor of the merge module or {@code null}.
236      *
237      * @see #setModuleVendor(java.lang.String)
238      */
239     public String getModuleVendor()
240     {
241         return this.moduleVendor;
242     }
243 
244     /**
245      * Sets the vendor of the merged module.
246      *
247      * @param value The new vendor of the merged module or {@code null}.
248      *
249      * @see #getModuleVendor()
250      */
251     public void setModuleVendor( final String value )
252     {
253         this.moduleVendor = value;
254     }
255 
256     /**
257      * Gets a set of module names to include.
258      * <p>
259      * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
260      * to the returned set will be present inside the object. This is why there is no {@code set} method for the
261      * module includes property.
262      * </p>
263      *
264      * @return A set of module names to include.
265      *
266      * @see #createModuleInclude()
267      */
268     public Set<NameType> getModuleIncludes()
269     {
270         if ( this.moduleIncludes == null )
271         {
272             this.moduleIncludes = new HashSet<NameType>();
273         }
274 
275         return this.moduleIncludes;
276     }
277 
278     /**
279      * Creates a new {@code moduleInclude} element instance.
280      *
281      * @return A new {@code moduleInclude} element instance.
282      *
283      * @see #getModuleIncludes()
284      */
285     public NameType createModuleInclude()
286     {
287         final NameType moduleInclude = new NameType();
288         this.getModuleIncludes().add( moduleInclude );
289         return moduleInclude;
290     }
291 
292     /**
293      * Gets a set of module names to exclude.
294      * <p>
295      * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
296      * to the returned set will be present inside the object. This is why there is no {@code set} method for the
297      * module excludes property.
298      * </p>
299      *
300      * @return A set of module names to exclude.
301      *
302      * @see #createModuleExclude()
303      */
304     public Set<NameType> getModuleExcludes()
305     {
306         if ( this.moduleExcludes == null )
307         {
308             this.moduleExcludes = new HashSet<NameType>();
309         }
310 
311         return this.moduleExcludes;
312     }
313 
314     /**
315      * Creates a new {@code moduleExclude} element instance.
316      *
317      * @return A new {@code moduleExclude} element instance.
318      *
319      * @see #getModuleExcludes()
320      */
321     public NameType createModuleExclude()
322     {
323         final NameType moduleExclude = new NameType();
324         this.getModuleExcludes().add( moduleExclude );
325         return moduleExclude;
326     }
327 
328     /**
329      * Gets the XSLT documents to use for transforming model objects.
330      * <p>
331      * This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
332      * to the returned list will be present inside the object. This is why there is no {@code set} method for the
333      * model object stylesheet resources property.
334      * </p>
335      *
336      * @return The XSLT documents to use for transforming model objects.
337      *
338      * @see #createModelObjectStylesheetResource()
339      */
340     public List<TransformerResourceType> getModelObjectStylesheetResources()
341     {
342         if ( this.modelObjectStylesheetResources == null )
343         {
344             this.modelObjectStylesheetResources = new LinkedList<TransformerResourceType>();
345         }
346 
347         return this.modelObjectStylesheetResources;
348     }
349 
350     /**
351      * Creates a new {@code modelObjectStylesheetResource} element instance.
352      *
353      * @return A new {@code modelObjectStylesheetResource} element instance.
354      *
355      * @see #getModelObjectStylesheetResources()
356      */
357     public TransformerResourceType createModelObjectStylesheetResource()
358     {
359         final TransformerResourceType modelObjectStylesheetResource = new TransformerResourceType();
360         this.getModelObjectStylesheetResources().add( modelObjectStylesheetResource );
361         return modelObjectStylesheetResource;
362     }
363 
364     /**
365      * {@inheritDoc}
366      */
367     @Override
368     public void preExecuteTask() throws BuildException
369     {
370         super.preExecuteTask();
371 
372         this.assertNotNull( "moduleFile", this.getModuleFile() );
373         this.assertNotNull( "moduleName", this.getModuleName() );
374         this.assertNamesNotNull( this.getModuleExcludes() );
375         this.assertNamesNotNull( this.getModuleIncludes() );
376         this.assertLocationsNotNull( this.getModelObjectStylesheetResources() );
377     }
378 
379     /**
380      * Merges module resources.
381      *
382      * @throws BuildException if merging module resources fails.
383      */
384     @Override
385     public void executeTask() throws BuildException
386     {
387         ProjectClassLoader classLoader = null;
388 
389         try
390         {
391             this.log( Messages.getMessage( "mergingModules", this.getModel() ) );
392 
393             classLoader = this.newProjectClassLoader();
394             final Modules modules = new Modules();
395             final Set<ResourceType> resources = new HashSet<ResourceType>( this.getModuleResources() );
396             final ModelContext context = this.newModelContext( classLoader );
397             final Marshaller marshaller = context.createMarshaller( this.getModel() );
398             final Unmarshaller unmarshaller = context.createUnmarshaller( this.getModel() );
399 
400             if ( this.isModelResourceValidationEnabled() )
401             {
402                 unmarshaller.setSchema( context.createSchema( this.getModel() ) );
403             }
404 
405             if ( resources.isEmpty() )
406             {
407                 final ResourceType defaultResource = new ResourceType();
408                 defaultResource.setLocation( DefaultModelProvider.getDefaultModuleLocation() );
409                 defaultResource.setOptional( true );
410                 resources.add( defaultResource );
411             }
412 
413             for ( final ResourceType resource : resources )
414             {
415                 final URL[] urls = this.getResources( context, resource.getLocation() );
416 
417                 if ( urls.length == 0 )
418                 {
419                     if ( resource.isOptional() )
420                     {
421                         this.logMessage( Level.WARNING, Messages.getMessage( "moduleResourceNotFound",
422                                                                              resource.getLocation() ) );
423 
424                     }
425                     else
426                     {
427                         throw new BuildException(
428                             Messages.getMessage( "moduleResourceNotFound", resource.getLocation() ),
429                             this.getLocation() );
430 
431                     }
432                 }
433 
434                 for ( int i = urls.length - 1; i >= 0; i-- )
435                 {
436                     URLConnection con = null;
437                     InputStream in = null;
438 
439                     try
440                     {
441                         this.logMessage( Level.FINEST, Messages.getMessage( "reading", urls[i].toExternalForm() ) );
442 
443                         con = urls[i].openConnection();
444                         con.setConnectTimeout( resource.getConnectTimeout() );
445                         con.setReadTimeout( resource.getReadTimeout() );
446                         con.connect();
447                         in = con.getInputStream();
448 
449                         final Source source = new StreamSource( in, urls[i].toURI().toASCIIString() );
450 
451                         Object o = unmarshaller.unmarshal( source );
452                         if ( o instanceof JAXBElement<?> )
453                         {
454                             o = ( (JAXBElement<?>) o ).getValue();
455                         }
456 
457                         if ( o instanceof Module )
458                         {
459                             modules.getModule().add( (Module) o );
460                         }
461                         else if ( o instanceof Modules )
462                         {
463                             modules.getModule().addAll( ( (Modules) o ).getModule() );
464                         }
465                         else
466                         {
467                             this.log( Messages.getMessage( "unsupportedModuleResource", urls[i].toExternalForm() ),
468                                       Project.MSG_WARN );
469 
470                         }
471 
472                         in.close();
473                         in = null;
474                     }
475                     catch ( final SocketTimeoutException e )
476                     {
477                         String message = Messages.getMessage( e );
478                         message = Messages.getMessage( "resourceTimeout", message != null ? " " + message : "" );
479 
480                         if ( resource.isOptional() )
481                         {
482                             this.getProject().log( message, e, Project.MSG_WARN );
483                         }
484                         else
485                         {
486                             throw new BuildException( message, e, this.getLocation() );
487                         }
488                     }
489                     catch ( final IOException e )
490                     {
491                         String message = Messages.getMessage( e );
492                         message = Messages.getMessage( "resourceFailure", message != null ? " " + message : "" );
493 
494                         if ( resource.isOptional() )
495                         {
496                             this.getProject().log( message, e, Project.MSG_WARN );
497                         }
498                         else
499                         {
500                             throw new BuildException( message, e, this.getLocation() );
501                         }
502                     }
503                     finally
504                     {
505                         try
506                         {
507                             if ( in != null )
508                             {
509                                 in.close();
510                             }
511                         }
512                         catch ( final IOException e )
513                         {
514                             this.logMessage( Level.SEVERE, Messages.getMessage( e ), e );
515                         }
516                         finally
517                         {
518                             if ( con instanceof HttpURLConnection )
519                             {
520                                 ( (HttpURLConnection) con ).disconnect();
521                             }
522                         }
523                     }
524                 }
525             }
526 
527             for ( final Iterator<Module> it = modules.getModule().iterator(); it.hasNext(); )
528             {
529                 final Module module = it.next();
530 
531                 if ( !this.isModuleIncluded( module ) || this.isModuleExcluded( module ) )
532                 {
533                     it.remove();
534                     this.log( Messages.getMessage( "excludingModule", module.getName() ) );
535                 }
536                 else
537                 {
538                     this.log( Messages.getMessage( "includingModule", module.getName() ) );
539                 }
540             }
541 
542             Module classpathModule = null;
543             if ( this.isModelObjectClasspathResolutionEnabled() )
544             {
545                 classpathModule = modules.getClasspathModule( Modules.getDefaultClasspathModuleName(), classLoader );
546 
547                 if ( classpathModule != null && modules.getModule( Modules.getDefaultClasspathModuleName() ) == null )
548                 {
549                     modules.getModule().add( classpathModule );
550                 }
551                 else
552                 {
553                     classpathModule = null;
554                 }
555             }
556 
557             final ModelValidationReport validationReport = context.validateModel(
558                 this.getModel(), new JAXBSource( marshaller, new ObjectFactory().createModules( modules ) ) );
559 
560             this.logValidationReport( context, validationReport );
561 
562             if ( !validationReport.isModelValid() )
563             {
564                 throw new ModelException( Messages.getMessage( "invalidModel", this.getModel() ) );
565             }
566 
567             if ( classpathModule != null )
568             {
569                 modules.getModule().remove( classpathModule );
570             }
571 
572             Module mergedModule = modules.getMergedModule( this.getModuleName() );
573             mergedModule.setVendor( this.getModuleVendor() );
574             mergedModule.setVersion( this.getModuleVersion() );
575 
576             for ( int i = 0, s0 = this.getModelObjectStylesheetResources().size(); i < s0; i++ )
577             {
578                 final Transformer transformer =
579                     this.getTransformer( this.getModelObjectStylesheetResources().get( i ) );
580 
581                 if ( transformer != null )
582                 {
583                     final JAXBSource source =
584                         new JAXBSource( marshaller, new ObjectFactory().createModule( mergedModule ) );
585 
586                     final JAXBResult result = new JAXBResult( unmarshaller );
587                     transformer.transform( source, result );
588 
589                     if ( result.getResult() instanceof JAXBElement<?>
590                              && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Module )
591                     {
592                         mergedModule = (Module) ( (JAXBElement<?>) result.getResult() ).getValue();
593                     }
594                     else
595                     {
596                         throw new BuildException( Messages.getMessage(
597                             "illegalTransformationResult",
598                             this.getModelObjectStylesheetResources().get( i ).getLocation() ), this.getLocation() );
599 
600                     }
601                 }
602             }
603 
604             this.log( Messages.getMessage( "writingEncoded", this.getModuleFile().getAbsolutePath(),
605                                            this.getModuleEncoding() ) );
606 
607             marshaller.setProperty( Marshaller.JAXB_ENCODING, this.getModuleEncoding() );
608             marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
609             marshaller.setSchema( context.createSchema( this.getModel() ) );
610             marshaller.marshal( new ObjectFactory().createModule( mergedModule ), this.getModuleFile() );
611 
612             classLoader.close();
613             classLoader = null;
614         }
615         catch ( final IOException e )
616         {
617             throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
618         }
619         catch ( final URISyntaxException e )
620         {
621             throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
622         }
623         catch ( final JAXBException e )
624         {
625             String message = Messages.getMessage( e );
626             if ( message == null )
627             {
628                 message = Messages.getMessage( e.getLinkedException() );
629             }
630 
631             throw new BuildException( message, e, this.getLocation() );
632         }
633         catch ( final TransformerConfigurationException e )
634         {
635             throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
636         }
637         catch ( final TransformerException e )
638         {
639             throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
640         }
641         catch ( final ModelException e )
642         {
643             throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
644         }
645         finally
646         {
647             try
648             {
649                 if ( classLoader != null )
650                 {
651                     classLoader.close();
652                 }
653             }
654             catch ( final IOException e )
655             {
656                 this.logMessage( Level.SEVERE, Messages.getMessage( e ), e );
657             }
658         }
659     }
660 
661     /**
662      * Tests inclusion of a given module based on property {@code moduleIncludes}.
663      *
664      * @param module The module to test.
665      *
666      * @return {@code true}, if {@code module} is included based on property {@code moduleIncludes}.
667      *
668      * @throws NullPointerException if {@code module} is {@code null}.
669      *
670      * @see #getModuleIncludes()
671      */
672     public boolean isModuleIncluded( final Module module )
673     {
674         if ( module == null )
675         {
676             throw new NullPointerException( "module" );
677         }
678 
679         for ( final NameType include : this.getModuleIncludes() )
680         {
681             if ( include.getName().equals( module.getName() ) )
682             {
683                 return true;
684             }
685         }
686 
687         return this.getModuleIncludes().isEmpty() ? true : false;
688     }
689 
690     /**
691      * Tests exclusion of a given module based on property {@code moduleExcludes}.
692      *
693      * @param module The module to test.
694      *
695      * @return {@code true}, if {@code module} is excluded based on property {@code moduleExcludes}.
696      *
697      * @throws NullPointerException if {@code module} is {@code null}.
698      *
699      * @see #getModuleExcludes()
700      */
701     public boolean isModuleExcluded( final Module module )
702     {
703         if ( module == null )
704         {
705             throw new NullPointerException( "module" );
706         }
707 
708         for ( final NameType exclude : this.getModuleExcludes() )
709         {
710             if ( exclude.getName().equals( module.getName() ) )
711             {
712                 return true;
713             }
714         }
715 
716         return false;
717     }
718 
719     /**
720      * {@inheritDoc}
721      */
722     @Override
723     public MergeModulesTask clone()
724     {
725         final MergeModulesTask clone = (MergeModulesTask) super.clone();
726         clone.moduleFile = this.moduleFile != null ? new File( this.moduleFile.getAbsolutePath() ) : null;
727 
728         if ( this.moduleExcludes != null )
729         {
730             clone.moduleExcludes = new HashSet<NameType>( this.moduleExcludes.size() );
731             for ( final NameType e : this.moduleExcludes )
732             {
733                 clone.moduleExcludes.add( e.clone() );
734             }
735         }
736 
737         if ( this.moduleIncludes != null )
738         {
739             clone.moduleIncludes = new HashSet<NameType>( this.moduleIncludes.size() );
740             for ( final NameType e : this.moduleIncludes )
741             {
742                 clone.moduleIncludes.add( e.clone() );
743             }
744         }
745 
746         if ( this.modelObjectStylesheetResources != null )
747         {
748             clone.modelObjectStylesheetResources =
749                 new ArrayList<TransformerResourceType>( this.modelObjectStylesheetResources.size() );
750 
751             for ( final TransformerResourceType e : this.modelObjectStylesheetResources )
752             {
753                 clone.modelObjectStylesheetResources.add( e.clone() );
754             }
755         }
756 
757         return clone;
758     }
759 
760 }