View Javadoc

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