View Javadoc
1   /*
2    *   Copyright (C) Christian Schulte <cs@schulte.it>, 2005-206
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 5043 2015-05-27 07:03:39Z 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.SocketTimeoutException;
39  import java.net.URISyntaxException;
40  import java.net.URL;
41  import java.net.URLConnection;
42  import java.util.ArrayList;
43  import java.util.HashSet;
44  import java.util.Iterator;
45  import java.util.LinkedList;
46  import java.util.List;
47  import java.util.Set;
48  import java.util.logging.Level;
49  import javax.xml.bind.JAXBElement;
50  import javax.xml.bind.JAXBException;
51  import javax.xml.bind.Marshaller;
52  import javax.xml.bind.Unmarshaller;
53  import javax.xml.bind.util.JAXBResult;
54  import javax.xml.bind.util.JAXBSource;
55  import javax.xml.transform.Source;
56  import javax.xml.transform.Transformer;
57  import javax.xml.transform.TransformerConfigurationException;
58  import javax.xml.transform.TransformerException;
59  import javax.xml.transform.stream.StreamSource;
60  import org.apache.tools.ant.BuildException;
61  import org.apache.tools.ant.Project;
62  import org.jomc.ant.types.NameType;
63  import org.jomc.ant.types.ResourceType;
64  import org.jomc.ant.types.TransformerResourceType;
65  import org.jomc.model.Module;
66  import org.jomc.model.Modules;
67  import org.jomc.model.ObjectFactory;
68  import org.jomc.model.modlet.DefaultModelProvider;
69  import org.jomc.modlet.ModelContext;
70  import org.jomc.modlet.ModelException;
71  import org.jomc.modlet.ModelValidationReport;
72  
73  /**
74   * Task for merging module resources.
75   *
76   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
77   * @version $JOMC: MergeModulesTask.java 5043 2015-05-27 07:03:39Z schulte $
78   */
79  public final class MergeModulesTask extends JomcModelTask
80  {
81  
82      /**
83       * The encoding of the module resource.
84       */
85      private String moduleEncoding;
86  
87      /**
88       * File to write the merged module to.
89       */
90      private File moduleFile;
91  
92      /**
93       * The name of the merged module.
94       */
95      private String moduleName;
96  
97      /**
98       * The version of the merged module.
99       */
100     private String moduleVersion;
101 
102     /**
103      * The vendor of the merged module.
104      */
105     private String moduleVendor;
106 
107     /**
108      * Included modules.
109      */
110     private Set<NameType> moduleIncludes;
111 
112     /**
113      * Excluded modules.
114      */
115     private Set<NameType> moduleExcludes;
116 
117     /**
118      * XSLT documents to use for transforming model objects.
119      */
120     private List<TransformerResourceType> modelObjectStylesheetResources;
121 
122     /**
123      * Creates a new {@code MergeModulesTask} instance.
124      */
125     public MergeModulesTask()
126     {
127         super();
128     }
129 
130     /**
131      * Gets the file to write the merged module to.
132      *
133      * @return The file to write the merged module to or {@code null}.
134      *
135      * @see #setModuleFile(java.io.File)
136      */
137     public File getModuleFile()
138     {
139         return this.moduleFile;
140     }
141 
142     /**
143      * Sets the file to write the merged module to.
144      *
145      * @param value The new file to write the merged module to or {@code null}.
146      *
147      * @see #getModuleFile()
148      */
149     public void setModuleFile( final File value )
150     {
151         this.moduleFile = value;
152     }
153 
154     /**
155      * Gets the encoding of the module resource.
156      *
157      * @return The encoding of the module resource.
158      *
159      * @see #setModuleEncoding(java.lang.String)
160      */
161     public String getModuleEncoding()
162     {
163         if ( this.moduleEncoding == null )
164         {
165             this.moduleEncoding = new OutputStreamWriter( new ByteArrayOutputStream() ).getEncoding();
166         }
167 
168         return this.moduleEncoding;
169     }
170 
171     /**
172      * Sets the encoding of the module resource.
173      *
174      * @param value The new encoding of the module resource or {@code null}.
175      *
176      * @see #getModuleEncoding()
177      */
178     public void setModuleEncoding( final String value )
179     {
180         this.moduleEncoding = value;
181     }
182 
183     /**
184      * Gets the name of the merged module.
185      *
186      * @return The name of the merged module or {@code null}.
187      *
188      * @see #setModuleName(java.lang.String)
189      */
190     public String getModuleName()
191     {
192         return this.moduleName;
193     }
194 
195     /**
196      * Sets the name of the merged module.
197      *
198      * @param value The new name of the merged module or {@code null}.
199      *
200      * @see #getModuleName()
201      */
202     public void setModuleName( final String value )
203     {
204         this.moduleName = value;
205     }
206 
207     /**
208      * Gets the version of the merged module.
209      *
210      * @return The version of the merged module or {@code null}.
211      *
212      * @see #setModuleVersion(java.lang.String)
213      */
214     public String getModuleVersion()
215     {
216         return this.moduleVersion;
217     }
218 
219     /**
220      * Sets the version of the merged module.
221      *
222      * @param value The new version of the merged module or {@code null}.
223      *
224      * @see #getModuleVersion()
225      */
226     public void setModuleVersion( final String value )
227     {
228         this.moduleVersion = value;
229     }
230 
231     /**
232      * Gets the vendor of the merged module.
233      *
234      * @return The vendor of the merge module or {@code null}.
235      *
236      * @see #setModuleVendor(java.lang.String)
237      */
238     public String getModuleVendor()
239     {
240         return this.moduleVendor;
241     }
242 
243     /**
244      * Sets the vendor of the merged module.
245      *
246      * @param value The new vendor of the merged module or {@code null}.
247      *
248      * @see #getModuleVendor()
249      */
250     public void setModuleVendor( final String value )
251     {
252         this.moduleVendor = value;
253     }
254 
255     /**
256      * Gets a set of module names to include.
257      * <p>
258      * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
259      * to the returned set will be present inside the object. This is why there is no {@code set} method for the
260      * module includes property.
261      * </p>
262      *
263      * @return A set of module names to include.
264      *
265      * @see #createModuleInclude()
266      */
267     public Set<NameType> getModuleIncludes()
268     {
269         if ( this.moduleIncludes == null )
270         {
271             this.moduleIncludes = new HashSet<NameType>();
272         }
273 
274         return this.moduleIncludes;
275     }
276 
277     /**
278      * Creates a new {@code moduleInclude} element instance.
279      *
280      * @return A new {@code moduleInclude} element instance.
281      *
282      * @see #getModuleIncludes()
283      */
284     public NameType createModuleInclude()
285     {
286         final NameType moduleInclude = new NameType();
287         this.getModuleIncludes().add( moduleInclude );
288         return moduleInclude;
289     }
290 
291     /**
292      * Gets a set of module names to exclude.
293      * <p>
294      * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
295      * to the returned set will be present inside the object. This is why there is no {@code set} method for the
296      * module excludes property.
297      * </p>
298      *
299      * @return A set of module names to exclude.
300      *
301      * @see #createModuleExclude()
302      */
303     public Set<NameType> getModuleExcludes()
304     {
305         if ( this.moduleExcludes == null )
306         {
307             this.moduleExcludes = new HashSet<NameType>();
308         }
309 
310         return this.moduleExcludes;
311     }
312 
313     /**
314      * Creates a new {@code moduleExclude} element instance.
315      *
316      * @return A new {@code moduleExclude} element instance.
317      *
318      * @see #getModuleExcludes()
319      */
320     public NameType createModuleExclude()
321     {
322         final NameType moduleExclude = new NameType();
323         this.getModuleExcludes().add( moduleExclude );
324         return moduleExclude;
325     }
326 
327     /**
328      * Gets the XSLT documents to use for transforming model objects.
329      * <p>
330      * This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
331      * to the returned list will be present inside the object. This is why there is no {@code set} method for the
332      * model object stylesheet resources property.
333      * </p>
334      *
335      * @return The XSLT documents to use for transforming model objects.
336      *
337      * @see #createModelObjectStylesheetResource()
338      */
339     public List<TransformerResourceType> getModelObjectStylesheetResources()
340     {
341         if ( this.modelObjectStylesheetResources == null )
342         {
343             this.modelObjectStylesheetResources = new LinkedList<TransformerResourceType>();
344         }
345 
346         return this.modelObjectStylesheetResources;
347     }
348 
349     /**
350      * Creates a new {@code modelObjectStylesheetResource} element instance.
351      *
352      * @return A new {@code modelObjectStylesheetResource} element instance.
353      *
354      * @see #getModelObjectStylesheetResources()
355      */
356     public TransformerResourceType createModelObjectStylesheetResource()
357     {
358         final TransformerResourceType modelObjectStylesheetResource = new TransformerResourceType();
359         this.getModelObjectStylesheetResources().add( modelObjectStylesheetResource );
360         return modelObjectStylesheetResource;
361     }
362 
363     /**
364      * {@inheritDoc}
365      */
366     @Override
367     public void preExecuteTask() throws BuildException
368     {
369         super.preExecuteTask();
370 
371         this.assertNotNull( "moduleFile", this.getModuleFile() );
372         this.assertNotNull( "moduleName", this.getModuleName() );
373         this.assertNamesNotNull( this.getModuleExcludes() );
374         this.assertNamesNotNull( this.getModuleIncludes() );
375         this.assertLocationsNotNull( this.getModelObjectStylesheetResources() );
376     }
377 
378     /**
379      * Merges module resources.
380      *
381      * @throws BuildException if merging module resources fails.
382      */
383     @Override
384     public void executeTask() throws BuildException
385     {
386         ProjectClassLoader classLoader = null;
387         boolean suppressExceptionOnClose = true;
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                     InputStream in = null;
437                     suppressExceptionOnClose = true;
438 
439                     try
440                     {
441                         this.logMessage( Level.FINEST, Messages.getMessage( "reading", urls[i].toExternalForm() ) );
442 
443                         final URLConnection 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                         suppressExceptionOnClose = false;
473                     }
474                     catch ( final SocketTimeoutException e )
475                     {
476                         String message = Messages.getMessage( e );
477                         message = Messages.getMessage( "resourceTimeout", message != null ? " " + message : "" );
478 
479                         if ( resource.isOptional() )
480                         {
481                             this.getProject().log( message, e, Project.MSG_WARN );
482                         }
483                         else
484                         {
485                             throw new BuildException( message, e, this.getLocation() );
486                         }
487                     }
488                     catch ( final IOException e )
489                     {
490                         String message = Messages.getMessage( e );
491                         message = Messages.getMessage( "resourceFailure", message != null ? " " + message : "" );
492 
493                         if ( resource.isOptional() )
494                         {
495                             this.getProject().log( message, e, Project.MSG_WARN );
496                         }
497                         else
498                         {
499                             throw new BuildException( message, e, this.getLocation() );
500                         }
501                     }
502                     finally
503                     {
504                         try
505                         {
506                             if ( in != null )
507                             {
508                                 in.close();
509                             }
510                         }
511                         catch ( final IOException e )
512                         {
513 
514                             if ( suppressExceptionOnClose )
515                             {
516                                 this.logMessage( Level.SEVERE, Messages.getMessage( e ), e );
517                             }
518                             else
519                             {
520                                 throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
521                             }
522                         }
523                     }
524                 }
525 
526                 suppressExceptionOnClose = true;
527             }
528 
529             for ( final Iterator<Module> it = modules.getModule().iterator(); it.hasNext(); )
530             {
531                 final Module module = it.next();
532 
533                 if ( !this.isModuleIncluded( module ) || this.isModuleExcluded( module ) )
534                 {
535                     it.remove();
536                     this.log( Messages.getMessage( "excludingModule", module.getName() ) );
537                 }
538                 else
539                 {
540                     this.log( Messages.getMessage( "includingModule", module.getName() ) );
541                 }
542             }
543 
544             Module classpathModule = null;
545             if ( this.isModelObjectClasspathResolutionEnabled() )
546             {
547                 classpathModule = modules.getClasspathModule( Modules.getDefaultClasspathModuleName(), classLoader );
548 
549                 if ( classpathModule != null && modules.getModule( Modules.getDefaultClasspathModuleName() ) == null )
550                 {
551                     modules.getModule().add( classpathModule );
552                 }
553                 else
554                 {
555                     classpathModule = null;
556                 }
557             }
558 
559             final ModelValidationReport validationReport = context.validateModel(
560                 this.getModel(), new JAXBSource( marshaller, new ObjectFactory().createModules( modules ) ) );
561 
562             this.logValidationReport( context, validationReport );
563 
564             if ( !validationReport.isModelValid() )
565             {
566                 throw new ModelException( Messages.getMessage( "invalidModel", this.getModel() ) );
567             }
568 
569             if ( classpathModule != null )
570             {
571                 modules.getModule().remove( classpathModule );
572             }
573 
574             Module mergedModule = modules.getMergedModule( this.getModuleName() );
575             mergedModule.setVendor( this.getModuleVendor() );
576             mergedModule.setVersion( this.getModuleVersion() );
577 
578             for ( int i = 0, s0 = this.getModelObjectStylesheetResources().size(); i < s0; i++ )
579             {
580                 final Transformer transformer =
581                     this.getTransformer( this.getModelObjectStylesheetResources().get( i ) );
582 
583                 if ( transformer != null )
584                 {
585                     final JAXBSource source =
586                         new JAXBSource( marshaller, new ObjectFactory().createModule( mergedModule ) );
587 
588                     final JAXBResult result = new JAXBResult( unmarshaller );
589                     transformer.transform( source, result );
590 
591                     if ( result.getResult() instanceof JAXBElement<?>
592                              && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Module )
593                     {
594                         mergedModule = (Module) ( (JAXBElement<?>) result.getResult() ).getValue();
595                     }
596                     else
597                     {
598                         throw new BuildException( Messages.getMessage(
599                             "illegalTransformationResult",
600                             this.getModelObjectStylesheetResources().get( i ).getLocation() ), this.getLocation() );
601 
602                     }
603                 }
604             }
605 
606             this.log( Messages.getMessage( "writingEncoded", this.getModuleFile().getAbsolutePath(),
607                                            this.getModuleEncoding() ) );
608 
609             marshaller.setProperty( Marshaller.JAXB_ENCODING, this.getModuleEncoding() );
610             marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
611             marshaller.setSchema( context.createSchema( this.getModel() ) );
612             marshaller.marshal( new ObjectFactory().createModule( mergedModule ), this.getModuleFile() );
613             suppressExceptionOnClose = false;
614         }
615         catch ( final URISyntaxException e )
616         {
617             throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
618         }
619         catch ( final JAXBException e )
620         {
621             String message = Messages.getMessage( e );
622             if ( message == null )
623             {
624                 message = Messages.getMessage( e.getLinkedException() );
625             }
626 
627             throw new BuildException( message, e, this.getLocation() );
628         }
629         catch ( final TransformerConfigurationException e )
630         {
631             throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
632         }
633         catch ( final TransformerException e )
634         {
635             throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
636         }
637         catch ( final ModelException e )
638         {
639             throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
640         }
641         finally
642         {
643             try
644             {
645                 if ( classLoader != null )
646                 {
647                     classLoader.close();
648                 }
649             }
650             catch ( final IOException e )
651             {
652                 if ( suppressExceptionOnClose )
653                 {
654                     this.logMessage( Level.SEVERE, Messages.getMessage( e ), e );
655                 }
656                 else
657                 {
658                     throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
659                 }
660             }
661         }
662     }
663 
664     /**
665      * Tests inclusion of a given module based on property {@code moduleIncludes}.
666      *
667      * @param module The module to test.
668      *
669      * @return {@code true}, if {@code module} is included based on property {@code moduleIncludes}.
670      *
671      * @throws NullPointerException if {@code module} is {@code null}.
672      *
673      * @see #getModuleIncludes()
674      */
675     public boolean isModuleIncluded( final Module module )
676     {
677         if ( module == null )
678         {
679             throw new NullPointerException( "module" );
680         }
681 
682         for ( final NameType include : this.getModuleIncludes() )
683         {
684             if ( include.getName().equals( module.getName() ) )
685             {
686                 return true;
687             }
688         }
689 
690         return this.getModuleIncludes().isEmpty() ? true : false;
691     }
692 
693     /**
694      * Tests exclusion of a given module based on property {@code moduleExcludes}.
695      *
696      * @param module The module to test.
697      *
698      * @return {@code true}, if {@code module} is excluded based on property {@code moduleExcludes}.
699      *
700      * @throws NullPointerException if {@code module} is {@code null}.
701      *
702      * @see #getModuleExcludes()
703      */
704     public boolean isModuleExcluded( final Module module )
705     {
706         if ( module == null )
707         {
708             throw new NullPointerException( "module" );
709         }
710 
711         for ( final NameType exclude : this.getModuleExcludes() )
712         {
713             if ( exclude.getName().equals( module.getName() ) )
714             {
715                 return true;
716             }
717         }
718 
719         return false;
720     }
721 
722     /**
723      * {@inheritDoc}
724      */
725     @Override
726     public MergeModulesTask clone()
727     {
728         final MergeModulesTask clone = (MergeModulesTask) super.clone();
729         clone.moduleFile = this.moduleFile != null ? new File( this.moduleFile.getAbsolutePath() ) : null;
730 
731         if ( this.moduleExcludes != null )
732         {
733             clone.moduleExcludes = new HashSet<NameType>( this.moduleExcludes.size() );
734             for ( final NameType e : this.moduleExcludes )
735             {
736                 clone.moduleExcludes.add( e.clone() );
737             }
738         }
739 
740         if ( this.moduleIncludes != null )
741         {
742             clone.moduleIncludes = new HashSet<NameType>( this.moduleIncludes.size() );
743             for ( final NameType e : this.moduleIncludes )
744             {
745                 clone.moduleIncludes.add( e.clone() );
746             }
747         }
748 
749         if ( this.modelObjectStylesheetResources != null )
750         {
751             clone.modelObjectStylesheetResources =
752                 new ArrayList<TransformerResourceType>( this.modelObjectStylesheetResources.size() );
753 
754             for ( final TransformerResourceType e : this.modelObjectStylesheetResources )
755             {
756                 clone.modelObjectStylesheetResources.add( e.clone() );
757             }
758         }
759 
760         return clone;
761     }
762 
763 }