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