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: JomcTask.java 5043 2015-05-27 07:03:39Z schulte $
29   *
30   */
31  package org.jomc.ant;
32  
33  import java.io.BufferedReader;
34  import java.io.File;
35  import java.io.IOException;
36  import java.io.InputStream;
37  import java.io.StringReader;
38  import java.io.StringWriter;
39  import java.net.MalformedURLException;
40  import java.net.SocketTimeoutException;
41  import java.net.URI;
42  import java.net.URISyntaxException;
43  import java.net.URL;
44  import java.net.URLConnection;
45  import java.util.ArrayList;
46  import java.util.Collection;
47  import java.util.Enumeration;
48  import java.util.HashSet;
49  import java.util.Iterator;
50  import java.util.LinkedList;
51  import java.util.List;
52  import java.util.Map;
53  import java.util.Properties;
54  import java.util.Set;
55  import java.util.logging.Level;
56  import javax.xml.bind.JAXBException;
57  import javax.xml.bind.Marshaller;
58  import javax.xml.transform.ErrorListener;
59  import javax.xml.transform.Transformer;
60  import javax.xml.transform.TransformerConfigurationException;
61  import javax.xml.transform.TransformerException;
62  import javax.xml.transform.TransformerFactory;
63  import javax.xml.transform.stream.StreamSource;
64  import org.apache.tools.ant.BuildException;
65  import org.apache.tools.ant.Project;
66  import org.apache.tools.ant.PropertyHelper;
67  import org.apache.tools.ant.Task;
68  import org.apache.tools.ant.types.Path;
69  import org.apache.tools.ant.types.Reference;
70  import org.jomc.ant.types.KeyValueType;
71  import org.jomc.ant.types.NameType;
72  import org.jomc.ant.types.PropertiesFormatType;
73  import org.jomc.ant.types.PropertiesResourceType;
74  import org.jomc.ant.types.ResourceType;
75  import org.jomc.ant.types.TransformerResourceType;
76  import org.jomc.model.ModelObject;
77  import org.jomc.modlet.DefaultModelContext;
78  import org.jomc.modlet.DefaultModletProvider;
79  import org.jomc.modlet.Model;
80  import org.jomc.modlet.ModelContext;
81  import org.jomc.modlet.ModelContextFactory;
82  import org.jomc.modlet.ModelException;
83  import org.jomc.modlet.ModelValidationReport;
84  import org.jomc.modlet.ModletProvider;
85  
86  /**
87   * Base class for executing tasks.
88   *
89   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
90   * @version $JOMC: JomcTask.java 5043 2015-05-27 07:03:39Z schulte $
91   * @see #execute()
92   */
93  public class JomcTask extends Task
94  {
95  
96      /**
97       * The class path to process.
98       */
99      private Path classpath;
100 
101     /**
102      * The identifier of the model to process.
103      */
104     private String model;
105 
106     /**
107      * {@code ModelContext} attributes to apply.
108      */
109     private List<KeyValueType> modelContextAttributes;
110 
111     /**
112      * The name of the {@code ModelContextFactory} implementation class backing the task.
113      */
114     private String modelContextFactoryClassName;
115 
116     /**
117      * Controls processing of models.
118      */
119     private boolean modelProcessingEnabled = true;
120 
121     /**
122      * The location to search for modlets.
123      */
124     private String modletLocation;
125 
126     /**
127      * The {@code http://jomc.org/modlet} namespace schema system id of the context backing the task.
128      */
129     private String modletSchemaSystemId;
130 
131     /**
132      * The location to search for providers.
133      */
134     private String providerLocation;
135 
136     /**
137      * The location to search for platform providers.
138      */
139     private String platformProviderLocation;
140 
141     /**
142      * The global transformation parameters to apply.
143      */
144     private List<KeyValueType> transformationParameters;
145 
146     /**
147      * The global transformation parameter resources to apply.
148      */
149     private List<PropertiesResourceType> transformationParameterResources;
150 
151     /**
152      * The global transformation output properties to apply.
153      */
154     private List<KeyValueType> transformationOutputProperties;
155 
156     /**
157      * The flag indicating JAXP schema validation of modlet resources is enabled.
158      */
159     private boolean modletResourceValidationEnabled = true;
160 
161     /**
162      * Property controlling the execution of the task.
163      */
164     private Object _if;
165 
166     /**
167      * Property controlling the execution of the task.
168      */
169     private Object unless;
170 
171     /**
172      * Creates a new {@code JomcTask} instance.
173      */
174     public JomcTask()
175     {
176         super();
177     }
178 
179     /**
180      * Gets an object controlling the execution of the task.
181      *
182      * @return An object controlling the execution of the task or {@code null}.
183      *
184      * @see #setIf(java.lang.Object)
185      */
186     public final Object getIf()
187     {
188         return this._if;
189     }
190 
191     /**
192      * Sets an object controlling the execution of the task.
193      *
194      * @param value The new object controlling the execution of the task or {@code null}.
195      *
196      * @see #getIf()
197      */
198     public final void setIf( final Object value )
199     {
200         this._if = value;
201     }
202 
203     /**
204      * Gets an object controlling the execution of the task.
205      *
206      * @return An object controlling the execution of the task or {@code null}.
207      *
208      * @see #setUnless(java.lang.Object)
209      */
210     public final Object getUnless()
211     {
212         if ( this.unless == null )
213         {
214             this.unless = Boolean.TRUE;
215         }
216 
217         return this.unless;
218     }
219 
220     /**
221      * Sets an object controlling the execution of the task.
222      *
223      * @param value The new object controlling the execution of the task or {@code null}.
224      *
225      * @see #getUnless()
226      */
227     public final void setUnless( final Object value )
228     {
229         this.unless = value;
230     }
231 
232     /**
233      * Creates a new {@code classpath} element instance.
234      *
235      * @return A new {@code classpath} element instance.
236      */
237     public final Path createClasspath()
238     {
239         return this.getClasspath().createPath();
240     }
241 
242     /**
243      * Gets the class path to process.
244      *
245      * @return The class path to process.
246      *
247      * @see #setClasspath(org.apache.tools.ant.types.Path)
248      */
249     public final Path getClasspath()
250     {
251         if ( this.classpath == null )
252         {
253             this.classpath = new Path( this.getProject() );
254         }
255 
256         return this.classpath;
257     }
258 
259     /**
260      * Adds to the class path to process.
261      *
262      * @param value The path to add to the list of class path elements.
263      *
264      * @see #getClasspath()
265      */
266     public final void setClasspath( final Path value )
267     {
268         this.getClasspath().add( value );
269     }
270 
271     /**
272      * Adds a reference to a class path defined elsewhere.
273      *
274      * @param value A reference to a class path.
275      *
276      * @see #getClasspath()
277      */
278     public final void setClasspathRef( final Reference value )
279     {
280         this.getClasspath().setRefid( value );
281     }
282 
283     /**
284      * Gets the identifier of the model to process.
285      *
286      * @return The identifier of the model to process.
287      *
288      * @see #setModel(java.lang.String)
289      */
290     public final String getModel()
291     {
292         if ( this.model == null )
293         {
294             this.model = ModelObject.MODEL_PUBLIC_ID;
295         }
296 
297         return this.model;
298     }
299 
300     /**
301      * Sets the identifier of the model to process.
302      *
303      * @param value The new identifier of the model to process or {@code null}.
304      *
305      * @see #getModel()
306      */
307     public final void setModel( final String value )
308     {
309         this.model = value;
310     }
311 
312     /**
313      * Gets the {@code ModelContext} attributes to apply.
314      * <p>
315      * This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
316      * to the returned list will be present inside the object. This is why there is no {@code set} method for the
317      * model context attributes property.
318      * </p>
319      *
320      * @return The {@code ModelContext} attributes to apply.
321      *
322      * @see #createModelContextAttribute()
323      * @see #newModelContext(java.lang.ClassLoader)
324      */
325     public final List<KeyValueType> getModelContextAttributes()
326     {
327         if ( this.modelContextAttributes == null )
328         {
329             this.modelContextAttributes = new LinkedList<KeyValueType>();
330         }
331 
332         return this.modelContextAttributes;
333     }
334 
335     /**
336      * Creates a new {@code modelContextAttribute} element instance.
337      *
338      * @return A new {@code modelContextAttribute} element instance.
339      *
340      * @see #getModelContextAttributes()
341      */
342     public KeyValueType createModelContextAttribute()
343     {
344         final KeyValueType modelContextAttribute = new KeyValueType();
345         this.getModelContextAttributes().add( modelContextAttribute );
346         return modelContextAttribute;
347     }
348 
349     /**
350      * Gets the name of the {@code ModelContextFactory} implementation class backing the task.
351      *
352      * @return The name of the {@code ModelContextFactory} implementation class backing the task or {@code null}.
353      *
354      * @see #setModelContextFactoryClassName(java.lang.String)
355      */
356     public final String getModelContextFactoryClassName()
357     {
358         return this.modelContextFactoryClassName;
359     }
360 
361     /**
362      * Sets the name of the {@code ModelContextFactory} implementation class backing the task.
363      *
364      * @param value The new name of the {@code ModelContextFactory} implementation class backing the task or
365      * {@code null}.
366      *
367      * @see #getModelContextFactoryClassName()
368      */
369     public final void setModelContextFactoryClassName( final String value )
370     {
371         this.modelContextFactoryClassName = value;
372     }
373 
374     /**
375      * Gets a flag indicating the processing of models is enabled.
376      *
377      * @return {@code true}, if processing of models is enabled; {@code false}, else.
378      *
379      * @see #setModelProcessingEnabled(boolean)
380      */
381     public final boolean isModelProcessingEnabled()
382     {
383         return this.modelProcessingEnabled;
384     }
385 
386     /**
387      * Sets the flag indicating the processing of models is enabled.
388      *
389      * @param value {@code true}, to enable processing of models; {@code false}, to disable processing of models.
390      *
391      * @see #isModelProcessingEnabled()
392      */
393     public final void setModelProcessingEnabled( final boolean value )
394     {
395         this.modelProcessingEnabled = value;
396     }
397 
398     /**
399      * Gets the location searched for modlets.
400      *
401      * @return The location searched for modlets or {@code null}.
402      *
403      * @see #setModletLocation(java.lang.String)
404      */
405     public final String getModletLocation()
406     {
407         return this.modletLocation;
408     }
409 
410     /**
411      * Sets the location to search for modlets.
412      *
413      * @param value The new location to search for modlets or {@code null}.
414      *
415      * @see #getModletLocation()
416      */
417     public final void setModletLocation( final String value )
418     {
419         this.modletLocation = value;
420     }
421 
422     /**
423      * Gets the {@code http://jomc.org/modlet} namespace schema system id of the context backing the task.
424      *
425      * @return The {@code http://jomc.org/modlet} namespace schema system id of the context backing the task or
426      * {@code null}.
427      *
428      * @see #setModletSchemaSystemId(java.lang.String)
429      */
430     public final String getModletSchemaSystemId()
431     {
432         return this.modletSchemaSystemId;
433     }
434 
435     /**
436      * Sets the {@code http://jomc.org/modlet} namespace schema system id of the context backing the task.
437      *
438      * @param value The new {@code http://jomc.org/modlet} namespace schema system id of the context backing the task or
439      * {@code null}.
440      *
441      * @see #getModletSchemaSystemId()
442      */
443     public final void setModletSchemaSystemId( final String value )
444     {
445         this.modletSchemaSystemId = value;
446     }
447 
448     /**
449      * Gets the location searched for providers.
450      *
451      * @return The location searched for providers or {@code null}.
452      *
453      * @see #setProviderLocation(java.lang.String)
454      */
455     public final String getProviderLocation()
456     {
457         return this.providerLocation;
458     }
459 
460     /**
461      * Sets the location to search for providers.
462      *
463      * @param value The new location to search for providers or {@code null}.
464      *
465      * @see #getProviderLocation()
466      */
467     public final void setProviderLocation( final String value )
468     {
469         this.providerLocation = value;
470     }
471 
472     /**
473      * Gets the location searched for platform provider resources.
474      *
475      * @return The location searched for platform provider resources or {@code null}.
476      *
477      * @see #setPlatformProviderLocation(java.lang.String)
478      */
479     public final String getPlatformProviderLocation()
480     {
481         return this.platformProviderLocation;
482     }
483 
484     /**
485      * Sets the location to search for platform provider resources.
486      *
487      * @param value The new location to search for platform provider resources or {@code null}.
488      *
489      * @see #getPlatformProviderLocation()
490      */
491     public final void setPlatformProviderLocation( final String value )
492     {
493         this.platformProviderLocation = value;
494     }
495 
496     /**
497      * Gets the global transformation parameters to apply.
498      * <p>
499      * This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
500      * to the returned list will be present inside the object. This is why there is no {@code set} method for the
501      * transformation parameters property.
502      * </p>
503      *
504      * @return The global transformation parameters to apply.
505      *
506      * @see #createTransformationParameter()
507      * @see #getTransformer(org.jomc.ant.types.TransformerResourceType)
508      */
509     public final List<KeyValueType> getTransformationParameters()
510     {
511         if ( this.transformationParameters == null )
512         {
513             this.transformationParameters = new LinkedList<KeyValueType>();
514         }
515 
516         return this.transformationParameters;
517     }
518 
519     /**
520      * Creates a new {@code transformationParameter} element instance.
521      *
522      * @return A new {@code transformationParameter} element instance.
523      *
524      * @see #getTransformationParameters()
525      */
526     public KeyValueType createTransformationParameter()
527     {
528         final KeyValueType transformationParameter = new KeyValueType();
529         this.getTransformationParameters().add( transformationParameter );
530         return transformationParameter;
531     }
532 
533     /**
534      * Gets the global transformation parameter resources to apply.
535      * <p>
536      * This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
537      * to the returned list will be present inside the object. This is why there is no {@code set} method for the
538      * transformation parameter resources property.
539      * </p>
540      *
541      * @return The global transformation parameter resources to apply.
542      *
543      * @see #createTransformationParameterResource()
544      * @see #getTransformer(org.jomc.ant.types.TransformerResourceType)
545      */
546     public final List<PropertiesResourceType> getTransformationParameterResources()
547     {
548         if ( this.transformationParameterResources == null )
549         {
550             this.transformationParameterResources = new LinkedList<PropertiesResourceType>();
551         }
552 
553         return this.transformationParameterResources;
554     }
555 
556     /**
557      * Creates a new {@code transformationParameterResource} element instance.
558      *
559      * @return A new {@code transformationParameterResource} element instance.
560      *
561      * @see #getTransformationParameterResources()
562      */
563     public PropertiesResourceType createTransformationParameterResource()
564     {
565         final PropertiesResourceType transformationParameterResource = new PropertiesResourceType();
566         this.getTransformationParameterResources().add( transformationParameterResource );
567         return transformationParameterResource;
568     }
569 
570     /**
571      * Gets the global transformation output properties to apply.
572      * <p>
573      * This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
574      * to the returned list will be present inside the object. This is why there is no {@code set} method for the
575      * transformation output properties property.
576      * </p>
577      *
578      * @return The global transformation output properties to apply.
579      *
580      * @see #createTransformationOutputProperty()
581      */
582     public final List<KeyValueType> getTransformationOutputProperties()
583     {
584         if ( this.transformationOutputProperties == null )
585         {
586             this.transformationOutputProperties = new LinkedList<KeyValueType>();
587         }
588 
589         return this.transformationOutputProperties;
590     }
591 
592     /**
593      * Creates a new {@code transformationOutputProperty} element instance.
594      *
595      * @return A new {@code transformationOutputProperty} element instance.
596      *
597      * @see #getTransformationOutputProperties()
598      */
599     public KeyValueType createTransformationOutputProperty()
600     {
601         final KeyValueType transformationOutputProperty = new KeyValueType();
602         this.getTransformationOutputProperties().add( transformationOutputProperty );
603         return transformationOutputProperty;
604     }
605 
606     /**
607      * Gets a flag indicating JAXP schema validation of modlet resources is enabled.
608      *
609      * @return {@code true}, if JAXP schema validation of modlet resources is enabled; {@code false}, else.
610      *
611      * @see #setModletResourceValidationEnabled(boolean)
612      */
613     public final boolean isModletResourceValidationEnabled()
614     {
615         return this.modletResourceValidationEnabled;
616     }
617 
618     /**
619      * Sets the flag indicating JAXP schema validation of modlet resources is enabled.
620      *
621      * @param value {@code true}, to enable JAXP schema validation of modlet resources; {@code false}, to disable JAXP
622      * schema validation of modlet resources.
623      *
624      * @see #isModletResourceValidationEnabled()
625      */
626     public final void setModletResourceValidationEnabled( final boolean value )
627     {
628         this.modletResourceValidationEnabled = value;
629     }
630 
631     /**
632      * Called by the project to let the task do its work.
633      *
634      * @throws BuildException if execution fails.
635      *
636      * @see #getIf()
637      * @see #getUnless()
638      * @see #preExecuteTask()
639      * @see #executeTask()
640      * @see #postExecuteTask()
641      */
642     @Override
643     public final void execute() throws BuildException
644     {
645         final PropertyHelper propertyHelper = PropertyHelper.getPropertyHelper( this.getProject() );
646 
647         if ( propertyHelper.testIfCondition( this.getIf() ) && !propertyHelper.testUnlessCondition( this.getUnless() ) )
648         {
649             try
650             {
651                 this.preExecuteTask();
652                 this.executeTask();
653             }
654             finally
655             {
656                 this.postExecuteTask();
657             }
658         }
659     }
660 
661     /**
662      * Called by the {@code execute} method prior to the {@code executeTask} method.
663      *
664      * @throws BuildException if execution fails.
665      *
666      * @see #execute()
667      */
668     public void preExecuteTask() throws BuildException
669     {
670         this.logSeparator();
671         this.log( Messages.getMessage( "title" ) );
672         this.logSeparator();
673 
674         this.assertNotNull( "model", this.getModel() );
675         this.assertKeysNotNull( this.getModelContextAttributes() );
676         this.assertKeysNotNull( this.getTransformationParameters() );
677         this.assertKeysNotNull( this.getTransformationOutputProperties() );
678         this.assertLocationsNotNull( this.getTransformationParameterResources() );
679     }
680 
681     /**
682      * Called by the {@code execute} method prior to the {@code postExecuteTask} method.
683      *
684      * @throws BuildException if execution fails.
685      *
686      * @see #execute()
687      */
688     public void executeTask() throws BuildException
689     {
690         this.getProject().log( Messages.getMessage( "unimplementedTask", this.getClass().getName(), "executeTask" ),
691                                Project.MSG_WARN );
692 
693     }
694 
695     /**
696      * Called by the {@code execute} method after the {@code preExecuteTask}/{@code executeTask} methods even if those
697      * methods threw an exception.
698      *
699      * @throws BuildException if execution fails.
700      *
701      * @see #execute()
702      */
703     public void postExecuteTask() throws BuildException
704     {
705         this.logSeparator();
706     }
707 
708     /**
709      * Gets a {@code Model} from a given {@code ModelContext}.
710      *
711      * @param context The context to get a {@code Model} from.
712      *
713      * @return The {@code Model} from {@code context}.
714      *
715      * @throws NullPointerException if {@code contexŧ} is {@code null}.
716      * @throws ModelException if getting the model fails.
717      *
718      * @see #getModel()
719      * @see #isModelProcessingEnabled()
720      */
721     public Model getModel( final ModelContext context ) throws ModelException
722     {
723         if ( context == null )
724         {
725             throw new NullPointerException( "context" );
726         }
727 
728         Model foundModel = context.findModel( this.getModel() );
729 
730         if ( foundModel != null && this.isModelProcessingEnabled() )
731         {
732             foundModel = context.processModel( foundModel );
733         }
734 
735         return foundModel;
736     }
737 
738     /**
739      * Creates an {@code URL} for a given resource location.
740      * <p>
741      * This method first searches the class path of the task for a single resource matching {@code location}. If
742      * such a resource is found, the URL of that resource is returned. If no such resource is found, an attempt is made
743      * to parse the given location to an URL. On successful parsing, that URL is returned. Failing that, the given
744      * location is interpreted as a file name relative to the project's base directory. If that file is found, the URL
745      * of that file is returned. Otherwise {@code null} is returned.
746      * </p>
747      *
748      * @param location The resource location to create an {@code URL} from.
749      *
750      * @return An {@code URL} for {@code location} or {@code null}, if parsing {@code location} to an URL fails and
751      * {@code location} points to a non-existent resource.
752      *
753      * @throws NullPointerException if {@code location} is {@code null}.
754      * @throws BuildException if creating an URL fails.
755      */
756     public URL getResource( final String location ) throws BuildException
757     {
758         if ( location == null )
759         {
760             throw new NullPointerException( "location" );
761         }
762 
763         try
764         {
765             String absolute = location;
766             if ( !absolute.startsWith( "/" ) )
767             {
768                 absolute = "/" + absolute;
769             }
770 
771             URL resource = this.getClass().getResource( absolute );
772             if ( resource == null )
773             {
774                 try
775                 {
776                     resource = new URL( location );
777                 }
778                 catch ( final MalformedURLException e )
779                 {
780                     this.log( e, Project.MSG_DEBUG );
781                     resource = null;
782                 }
783             }
784 
785             if ( resource == null )
786             {
787                 final File f = this.getProject().resolveFile( location );
788 
789                 if ( f.isFile() )
790                 {
791                     resource = f.toURI().toURL();
792                 }
793             }
794 
795             return resource;
796         }
797         catch ( final MalformedURLException e )
798         {
799             String m = Messages.getMessage( e );
800             m = m == null ? "" : " " + m;
801 
802             throw new BuildException( Messages.getMessage( "malformedLocation", location, m ), e, this.getLocation() );
803         }
804     }
805 
806     /**
807      * Creates an array of {@code URL}s for a given resource location.
808      * <p>
809      * This method first searches the given context for resources matching {@code location}. If such resources are
810      * found, an array of URLs of those resources is returned. If no such resources are found, an attempt is made
811      * to parse the given location to an URL. On successful parsing, that URL is returned. Failing that, the given
812      * location is interpreted as a file name relative to the project's base directory. If that file is found, the URL
813      * of that file is returned. Otherwise an empty array is returned.
814      * </p>
815      *
816      * @param context The context to search for resources.
817      * @param location The resource location to create an array of {@code URL}s from.
818      *
819      * @return An array of {@code URL}s for {@code location} or an empty array if parsing {@code location} to an URL
820      * fails and {@code location} points to non-existent resources.
821      *
822      * @throws NullPointerException if {@code context} or {@code location} is {@code null}.
823      * @throws BuildException if creating an URL array fails.
824      */
825     public URL[] getResources( final ModelContext context, final String location ) throws BuildException
826     {
827         if ( context == null )
828         {
829             throw new NullPointerException( "context" );
830         }
831         if ( location == null )
832         {
833             throw new NullPointerException( "location" );
834         }
835 
836         final Set<URI> uris = new HashSet<URI>();
837 
838         try
839         {
840             for ( final Enumeration<URL> e = context.findResources( location ); e.hasMoreElements(); )
841             {
842                 uris.add( e.nextElement().toURI() );
843             }
844         }
845         catch ( final URISyntaxException e )
846         {
847             this.log( e, Project.MSG_DEBUG );
848         }
849         catch ( final ModelException e )
850         {
851             this.log( e, Project.MSG_DEBUG );
852         }
853 
854         if ( uris.isEmpty() )
855         {
856             try
857             {
858                 uris.add( new URL( location ).toURI() );
859             }
860             catch ( final MalformedURLException e )
861             {
862                 this.log( e, Project.MSG_DEBUG );
863             }
864             catch ( final URISyntaxException e )
865             {
866                 this.log( e, Project.MSG_DEBUG );
867             }
868         }
869 
870         if ( uris.isEmpty() )
871         {
872             final File f = this.getProject().resolveFile( location );
873 
874             if ( f.isFile() )
875             {
876                 uris.add( f.toURI() );
877             }
878         }
879 
880         int i = 0;
881         final URL[] urls = new URL[ uris.size() ];
882 
883         for ( final URI uri : uris )
884         {
885             try
886             {
887                 urls[i++] = uri.toURL();
888             }
889             catch ( final MalformedURLException e )
890             {
891                 String m = Messages.getMessage( e );
892                 m = m == null ? "" : " " + m;
893 
894                 throw new BuildException( Messages.getMessage( "malformedLocation", uri.toASCIIString(), m ), e,
895                                           this.getLocation() );
896 
897             }
898         }
899 
900         return urls;
901     }
902 
903     /**
904      * Creates an {@code URL} for a given directory location.
905      * <p>
906      * This method first attempts to parse the given location to an URL. On successful parsing, that URL is returned.
907      * Failing that, the given location is interpreted as a directory name relative to the project's base directory. If
908      * that directory is found, the URL of that directory is returned. Otherwise {@code null} is returned.
909      * </p>
910      *
911      * @param location The directory location to create an {@code URL} from.
912      *
913      * @return An {@code URL} for {@code location} or {@code null}, if parsing {@code location} to an URL fails and
914      * {@code location} points to a non-existent directory.
915      *
916      * @throws NullPointerException if {@code location} is {@code null}.
917      * @throws BuildException if creating an URL fails.
918      */
919     public URL getDirectory( final String location ) throws BuildException
920     {
921         if ( location == null )
922         {
923             throw new NullPointerException( "location" );
924         }
925 
926         try
927         {
928             URL resource = null;
929 
930             try
931             {
932                 resource = new URL( location );
933             }
934             catch ( final MalformedURLException e )
935             {
936                 this.log( e, Project.MSG_DEBUG );
937                 resource = null;
938             }
939 
940             if ( resource == null )
941             {
942                 final File f = this.getProject().resolveFile( location );
943 
944                 if ( f.isDirectory() )
945                 {
946                     resource = f.toURI().toURL();
947                 }
948             }
949 
950             return resource;
951         }
952         catch ( final MalformedURLException e )
953         {
954             String m = Messages.getMessage( e );
955             m = m == null ? "" : " " + m;
956 
957             throw new BuildException( Messages.getMessage( "malformedLocation", location, m ), e, this.getLocation() );
958         }
959     }
960 
961     /**
962      * Creates a new {@code Transformer} for a given {@code TransformerResourceType}.
963      *
964      * @param resource The resource to create a {@code Transformer} of.
965      *
966      * @return A new {@code Transformer} for {@code resource} or {@code null}, if {@code resource} is not found and
967      * flagged optional.
968      *
969      * @throws TransformerConfigurationException if creating a new {@code Transformer} fails.
970      *
971      * @see #getTransformationParameterResources()
972      * @see #getTransformationParameters()
973      * @see #getResource(java.lang.String)
974      */
975     public Transformer getTransformer( final TransformerResourceType resource ) throws TransformerConfigurationException
976     {
977         if ( resource == null )
978         {
979             throw new NullPointerException( "resource" );
980         }
981 
982         InputStream in = null;
983         boolean suppressExceptionOnClose = true;
984         final URL url = this.getResource( resource.getLocation() );
985 
986         try
987         {
988             if ( url != null )
989             {
990                 final ErrorListener errorListener = new ErrorListener()
991                 {
992 
993                     public void warning( final TransformerException exception ) throws TransformerException
994                     {
995                         if ( getProject() != null )
996                         {
997                             getProject().log( Messages.getMessage( exception ), exception, Project.MSG_WARN );
998                         }
999                     }
1000 
1001                     public void error( final TransformerException exception ) throws TransformerException
1002                     {
1003                         throw exception;
1004                     }
1005 
1006                     public void fatalError( final TransformerException exception ) throws TransformerException
1007                     {
1008                         throw exception;
1009                     }
1010 
1011                 };
1012 
1013                 final URLConnection con = url.openConnection();
1014                 con.setConnectTimeout( resource.getConnectTimeout() );
1015                 con.setReadTimeout( resource.getReadTimeout() );
1016                 con.connect();
1017                 in = con.getInputStream();
1018 
1019                 final TransformerFactory f = TransformerFactory.newInstance();
1020                 f.setErrorListener( errorListener );
1021                 final Transformer transformer = f.newTransformer( new StreamSource( in, url.toURI().toASCIIString() ) );
1022                 transformer.setErrorListener( errorListener );
1023 
1024                 for ( final Map.Entry<Object, Object> e : System.getProperties().entrySet() )
1025                 {
1026                     transformer.setParameter( e.getKey().toString(), e.getValue() );
1027                 }
1028 
1029                 for ( final Iterator<Map.Entry<?, ?>> it = this.getProject().getProperties().entrySet().iterator();
1030                       it.hasNext(); )
1031                 {
1032                     final Map.Entry<?, ?> e = it.next();
1033                     transformer.setParameter( e.getKey().toString(), e.getValue() );
1034                 }
1035 
1036                 for ( int i = 0, s0 = this.getTransformationParameterResources().size(); i < s0; i++ )
1037                 {
1038                     for ( final Map.Entry<Object, Object> e
1039                               : this.getProperties( this.getTransformationParameterResources().get( i ) ).entrySet() )
1040                     {
1041                         transformer.setParameter( e.getKey().toString(), e.getValue() );
1042                     }
1043                 }
1044 
1045                 for ( int i = 0, s0 = this.getTransformationParameters().size(); i < s0; i++ )
1046                 {
1047                     final KeyValueType p = this.getTransformationParameters().get( i );
1048                     transformer.setParameter( p.getKey(), p.getObject( this.getLocation() ) );
1049                 }
1050 
1051                 for ( int i = 0, s0 = this.getTransformationOutputProperties().size(); i < s0; i++ )
1052                 {
1053                     final KeyValueType p = this.getTransformationOutputProperties().get( i );
1054                     transformer.setOutputProperty( p.getKey(), p.getValue() );
1055                 }
1056 
1057                 for ( int i = 0, s0 = resource.getTransformationParameterResources().size(); i < s0; i++ )
1058                 {
1059                     for ( final Map.Entry<Object, Object> e
1060                               : this.getProperties( resource.getTransformationParameterResources().get( i ) ).
1061                         entrySet() )
1062                     {
1063                         transformer.setParameter( e.getKey().toString(), e.getValue() );
1064                     }
1065                 }
1066 
1067                 for ( int i = 0, s0 = resource.getTransformationParameters().size(); i < s0; i++ )
1068                 {
1069                     final KeyValueType p = resource.getTransformationParameters().get( i );
1070                     transformer.setParameter( p.getKey(), p.getObject( this.getLocation() ) );
1071                 }
1072 
1073                 for ( int i = 0, s0 = resource.getTransformationOutputProperties().size(); i < s0; i++ )
1074                 {
1075                     final KeyValueType p = resource.getTransformationOutputProperties().get( i );
1076                     transformer.setOutputProperty( p.getKey(), p.getValue() );
1077                 }
1078 
1079                 suppressExceptionOnClose = false;
1080                 return transformer;
1081             }
1082             else if ( resource.isOptional() )
1083             {
1084                 this.log( Messages.getMessage( "transformerNotFound", resource.getLocation() ), Project.MSG_WARN );
1085             }
1086             else
1087             {
1088                 throw new BuildException( Messages.getMessage( "transformerNotFound", resource.getLocation() ),
1089                                           this.getLocation() );
1090 
1091             }
1092         }
1093         catch ( final URISyntaxException e )
1094         {
1095             throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1096         }
1097         catch ( final SocketTimeoutException e )
1098         {
1099             final String message = Messages.getMessage( e );
1100 
1101             if ( resource.isOptional() )
1102             {
1103                 this.getProject().log( Messages.getMessage( "resourceTimeout", message != null ? " " + message : "" ),
1104                                        e, Project.MSG_WARN );
1105 
1106             }
1107             else
1108             {
1109                 throw new BuildException( Messages.getMessage( "resourceTimeout", message != null ? " " + message : "" ),
1110                                           e, this.getLocation() );
1111 
1112             }
1113         }
1114         catch ( final IOException e )
1115         {
1116             final String message = Messages.getMessage( e );
1117 
1118             if ( resource.isOptional() )
1119             {
1120                 this.getProject().log( Messages.getMessage( "resourceFailure", message != null ? " " + message : "" ),
1121                                        e, Project.MSG_WARN );
1122 
1123             }
1124             else
1125             {
1126                 throw new BuildException( Messages.getMessage( "resourceFailure", message != null ? " " + message : "" ),
1127                                           e, this.getLocation() );
1128 
1129             }
1130         }
1131         finally
1132         {
1133             try
1134             {
1135                 if ( in != null )
1136                 {
1137                     in.close();
1138                 }
1139             }
1140             catch ( final IOException e )
1141             {
1142                 if ( suppressExceptionOnClose )
1143                 {
1144                     this.logMessage( Level.SEVERE, Messages.getMessage( e ), e );
1145                 }
1146                 else
1147                 {
1148                     throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1149                 }
1150             }
1151         }
1152 
1153         return null;
1154     }
1155 
1156     /**
1157      * Creates a new {@code Properties} instance from a {@code PropertiesResourceType}.
1158      *
1159      * @param propertiesResourceType The {@code PropertiesResourceType} specifying the properties to create.
1160      *
1161      * @return The properties for {@code propertiesResourceType}.
1162      *
1163      * @throws NullPointerException if {@code propertiesResourceType} is {@code null}.
1164      * @throws BuildException if loading properties fails.
1165      */
1166     public Properties getProperties( final PropertiesResourceType propertiesResourceType ) throws BuildException
1167     {
1168         if ( propertiesResourceType == null )
1169         {
1170             throw new NullPointerException( "propertiesResourceType" );
1171         }
1172 
1173         InputStream in = null;
1174         boolean suppressExceptionOnClose = true;
1175         final Properties properties = new Properties();
1176         final URL url = this.getResource( propertiesResourceType.getLocation() );
1177 
1178         try
1179         {
1180             if ( url != null )
1181             {
1182                 final URLConnection con = url.openConnection();
1183                 con.setConnectTimeout( propertiesResourceType.getConnectTimeout() );
1184                 con.setReadTimeout( propertiesResourceType.getReadTimeout() );
1185                 con.connect();
1186 
1187                 in = con.getInputStream();
1188 
1189                 if ( propertiesResourceType.getFormat() == PropertiesFormatType.PLAIN )
1190                 {
1191                     properties.load( in );
1192                 }
1193                 else if ( propertiesResourceType.getFormat() == PropertiesFormatType.XML )
1194                 {
1195                     properties.loadFromXML( in );
1196                 }
1197             }
1198             else if ( propertiesResourceType.isOptional() )
1199             {
1200                 this.log( Messages.getMessage( "propertiesNotFound", propertiesResourceType.getLocation() ),
1201                           Project.MSG_WARN );
1202 
1203             }
1204             else
1205             {
1206                 throw new BuildException( Messages.getMessage(
1207                     "propertiesNotFound", propertiesResourceType.getLocation() ), this.getLocation() );
1208 
1209             }
1210 
1211             suppressExceptionOnClose = false;
1212         }
1213         catch ( final SocketTimeoutException e )
1214         {
1215             final String message = Messages.getMessage( e );
1216 
1217             if ( propertiesResourceType.isOptional() )
1218             {
1219                 this.getProject().log( Messages.getMessage( "resourceTimeout", message != null ? " " + message : "" ),
1220                                        e, Project.MSG_WARN );
1221 
1222             }
1223             else
1224             {
1225                 throw new BuildException( Messages.getMessage( "resourceTimeout", message != null ? " " + message : "" ),
1226                                           e, this.getLocation() );
1227 
1228             }
1229         }
1230         catch ( final IOException e )
1231         {
1232             final String message = Messages.getMessage( e );
1233 
1234             if ( propertiesResourceType.isOptional() )
1235             {
1236                 this.getProject().log( Messages.getMessage( "resourceFailure", message != null ? " " + message : "" ),
1237                                        e, Project.MSG_WARN );
1238 
1239             }
1240             else
1241             {
1242                 throw new BuildException( Messages.getMessage( "resourceFailure", message != null ? " " + message : "" ),
1243                                           e, this.getLocation() );
1244 
1245             }
1246         }
1247         finally
1248         {
1249             try
1250             {
1251                 if ( in != null )
1252                 {
1253                     in.close();
1254                 }
1255             }
1256             catch ( final IOException e )
1257             {
1258                 if ( suppressExceptionOnClose )
1259                 {
1260                     this.logMessage( Level.SEVERE, Messages.getMessage( e ), e );
1261                 }
1262                 else
1263                 {
1264                     throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1265                 }
1266             }
1267         }
1268 
1269         return properties;
1270     }
1271 
1272     /**
1273      * Creates a new {@code ProjectClassLoader} instance.
1274      *
1275      * @return A new {@code ProjectClassLoader} instance.
1276      *
1277      * @throws BuildException if creating a new class loader instance fails.
1278      */
1279     public ProjectClassLoader newProjectClassLoader() throws BuildException
1280     {
1281         try
1282         {
1283             final ProjectClassLoader classLoader = new ProjectClassLoader( this.getProject(), this.getClasspath() );
1284             classLoader.getModletExcludes().addAll( ProjectClassLoader.getDefaultModletExcludes() );
1285             classLoader.getProviderExcludes().addAll( ProjectClassLoader.getDefaultProviderExcludes() );
1286             classLoader.getSchemaExcludes().addAll( ProjectClassLoader.getDefaultSchemaExcludes() );
1287             classLoader.getServiceExcludes().addAll( ProjectClassLoader.getDefaultServiceExcludes() );
1288 
1289             if ( this.getModletLocation() != null )
1290             {
1291                 classLoader.getModletResourceLocations().add( this.getModletLocation() );
1292             }
1293             else
1294             {
1295                 classLoader.getModletResourceLocations().add( DefaultModletProvider.getDefaultModletLocation() );
1296             }
1297 
1298             if ( this.getProviderLocation() != null )
1299             {
1300                 classLoader.getProviderResourceLocations().add(
1301                     this.getProviderLocation() + "/" + ModletProvider.class.getName() );
1302 
1303             }
1304             else
1305             {
1306                 classLoader.getProviderResourceLocations().add(
1307                     DefaultModelContext.getDefaultProviderLocation() + "/" + ModletProvider.class.getName() );
1308 
1309             }
1310 
1311             return classLoader;
1312         }
1313         catch ( final IOException e )
1314         {
1315             throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1316         }
1317     }
1318 
1319     /**
1320      * Creates a new {@code ModelContext} instance using a given class loader.
1321      *
1322      * @param classLoader The class loader to create a new {@code ModelContext} instance with.
1323      *
1324      * @return A new {@code ModelContext} instance backed by {@code classLoader}.
1325      *
1326      * @throws ModelException if creating a new {@code ModelContext} instance fails.
1327      */
1328     public ModelContext newModelContext( final ClassLoader classLoader ) throws ModelException
1329     {
1330         final ModelContextFactory modelContextFactory;
1331         if ( this.modelContextFactoryClassName != null )
1332         {
1333             modelContextFactory = ModelContextFactory.newInstance( this.getModelContextFactoryClassName() );
1334         }
1335         else
1336         {
1337             modelContextFactory = ModelContextFactory.newInstance();
1338         }
1339 
1340         final ModelContext modelContext = modelContextFactory.newModelContext( classLoader );
1341         modelContext.setLogLevel( Level.ALL );
1342         modelContext.setModletSchemaSystemId( this.getModletSchemaSystemId() );
1343 
1344         modelContext.getListeners().add( new ModelContext.Listener()
1345         {
1346 
1347             @Override
1348             public void onLog( final Level level, final String message, final Throwable t )
1349             {
1350                 super.onLog( level, message, t );
1351                 logMessage( level, message, t );
1352             }
1353 
1354         } );
1355 
1356         if ( this.getProviderLocation() != null )
1357         {
1358             modelContext.setAttribute( DefaultModelContext.PROVIDER_LOCATION_ATTRIBUTE_NAME,
1359                                        this.getProviderLocation() );
1360 
1361         }
1362 
1363         if ( this.getPlatformProviderLocation() != null )
1364         {
1365             modelContext.setAttribute( DefaultModelContext.PLATFORM_PROVIDER_LOCATION_ATTRIBUTE_NAME,
1366                                        this.getPlatformProviderLocation() );
1367 
1368         }
1369 
1370         if ( this.getModletLocation() != null )
1371         {
1372             modelContext.setAttribute( DefaultModletProvider.MODLET_LOCATION_ATTRIBUTE_NAME, this.getModletLocation() );
1373         }
1374 
1375         modelContext.setAttribute( DefaultModletProvider.VALIDATING_ATTRIBUTE_NAME,
1376                                    this.isModletResourceValidationEnabled() );
1377 
1378         for ( int i = 0, s0 = this.getModelContextAttributes().size(); i < s0; i++ )
1379         {
1380             final KeyValueType kv = this.getModelContextAttributes().get( i );
1381             final Object object = kv.getObject( this.getLocation() );
1382 
1383             if ( object != null )
1384             {
1385                 modelContext.setAttribute( kv.getKey(), object );
1386             }
1387             else
1388             {
1389                 modelContext.clearAttribute( kv.getKey() );
1390             }
1391         }
1392 
1393         return modelContext;
1394     }
1395 
1396     /**
1397      * Throws a {@code BuildException} on a given {@code null} value.
1398      *
1399      * @param attributeName The name of a mandatory attribute.
1400      * @param value The value of that attribute.
1401      *
1402      * @throws NullPointerException if {@code attributeName} is {@code null}.
1403      * @throws BuildException if {@code value} is {@code null}.
1404      */
1405     public final void assertNotNull( final String attributeName, final Object value ) throws BuildException
1406     {
1407         if ( attributeName == null )
1408         {
1409             throw new NullPointerException( "attributeName" );
1410         }
1411 
1412         if ( value == null )
1413         {
1414             throw new BuildException( Messages.getMessage( "mandatoryAttribute", attributeName ), this.getLocation() );
1415         }
1416     }
1417 
1418     /**
1419      * Throws a {@code BuildException} on a {@code null} value of a {@code name} property of a given {@code NameType}
1420      * collection.
1421      *
1422      * @param names The collection holding the {@code NameType} instances to test.
1423      *
1424      * @throws NullPointerException if {@code names} is {@code null}.
1425      * @throws BuildException if a {@code name} property of a given {@code NameType} from the {@code names} collection
1426      * holds a {@code null} value.
1427      */
1428     public final void assertNamesNotNull( final Collection<? extends NameType> names ) throws BuildException
1429     {
1430         if ( names == null )
1431         {
1432             throw new NullPointerException( "names" );
1433         }
1434 
1435         for ( final NameType n : names )
1436         {
1437             this.assertNotNull( "name", n.getName() );
1438         }
1439     }
1440 
1441     /**
1442      * Throws a {@code BuildException} on a {@code null} value of a {@code key} property of a given {@code KeyValueType}
1443      * collection.
1444      *
1445      * @param keys The collection holding the {@code KeyValueType} instances to test.
1446      *
1447      * @throws NullPointerException if {@code keys} is {@code null}.
1448      * @throws BuildException if a {@code key} property of a given {@code KeyValueType} from the {@code keys} collection
1449      * holds a {@code null} value.
1450      */
1451     public final void assertKeysNotNull( final Collection<? extends KeyValueType> keys ) throws BuildException
1452     {
1453         if ( keys == null )
1454         {
1455             throw new NullPointerException( "keys" );
1456         }
1457 
1458         for ( final KeyValueType k : keys )
1459         {
1460             this.assertNotNull( "key", k.getKey() );
1461         }
1462     }
1463 
1464     /**
1465      * Throws a {@code BuildException} on a {@code null} value of a {@code location} property of a given
1466      * {@code ResourceType} collection.
1467      *
1468      * @param locations The collection holding the {@code ResourceType} instances to test.
1469      *
1470      * @throws NullPointerException if {@code locations} is {@code null}.
1471      * @throws BuildException if a {@code location} property of a given {@code ResourceType} from the {@code locations}
1472      * collection holds a {@code null} value.
1473      */
1474     public final void assertLocationsNotNull( final Collection<? extends ResourceType> locations )
1475         throws BuildException
1476     {
1477         if ( locations == null )
1478         {
1479             throw new NullPointerException( "locations" );
1480         }
1481 
1482         for ( final ResourceType r : locations )
1483         {
1484             assertNotNull( "location", r.getLocation() );
1485 
1486             if ( r instanceof TransformerResourceType )
1487             {
1488                 assertKeysNotNull( ( (TransformerResourceType) r ).getTransformationParameters() );
1489                 assertLocationsNotNull( ( (TransformerResourceType) r ).getTransformationParameterResources() );
1490                 assertKeysNotNull( ( (TransformerResourceType) r ).getTransformationOutputProperties() );
1491             }
1492         }
1493     }
1494 
1495     /**
1496      * Logs a separator string.
1497      */
1498     public final void logSeparator()
1499     {
1500         this.log( Messages.getMessage( "separator" ) );
1501     }
1502 
1503     /**
1504      * Logs a message at a given level.
1505      *
1506      * @param level The level to log at.
1507      * @param message The message to log.
1508      *
1509      * @throws BuildException if logging fails.
1510      */
1511     public final void logMessage( final Level level, final String message ) throws BuildException
1512     {
1513         BufferedReader reader = null;
1514         boolean suppressExceptionOnClose = true;
1515 
1516         try
1517         {
1518             String line = null;
1519             reader = new BufferedReader( new StringReader( message ) );
1520 
1521             while ( ( line = reader.readLine() ) != null )
1522             {
1523                 if ( level.intValue() >= Level.SEVERE.intValue() )
1524                 {
1525                     log( line, Project.MSG_ERR );
1526                 }
1527                 else if ( level.intValue() >= Level.WARNING.intValue() )
1528                 {
1529                     log( line, Project.MSG_WARN );
1530                 }
1531                 else if ( level.intValue() >= Level.INFO.intValue() )
1532                 {
1533                     log( line, Project.MSG_INFO );
1534                 }
1535                 else
1536                 {
1537                     log( line, Project.MSG_DEBUG );
1538                 }
1539             }
1540 
1541             suppressExceptionOnClose = false;
1542         }
1543         catch ( final IOException e )
1544         {
1545             throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1546         }
1547         finally
1548         {
1549             try
1550             {
1551                 if ( reader != null )
1552                 {
1553                     reader.close();
1554                 }
1555             }
1556             catch ( final IOException e )
1557             {
1558                 if ( suppressExceptionOnClose )
1559                 {
1560                     this.log( e, Project.MSG_ERR );
1561                 }
1562                 else
1563                 {
1564                     throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1565                 }
1566             }
1567         }
1568     }
1569 
1570     /**
1571      * Logs a message at a given level.
1572      *
1573      * @param level The level to log at.
1574      * @param message The message to log.
1575      * @param throwable The throwable to log.
1576      *
1577      * @throws BuildException if logging fails.
1578      */
1579     public final void logMessage( final Level level, final String message, final Throwable throwable )
1580         throws BuildException
1581     {
1582         this.logMessage( level, message );
1583 
1584         if ( level.intValue() >= Level.SEVERE.intValue() )
1585         {
1586             log( throwable, Project.MSG_ERR );
1587         }
1588         else if ( level.intValue() >= Level.WARNING.intValue() )
1589         {
1590             log( throwable, Project.MSG_WARN );
1591         }
1592         else if ( level.intValue() >= Level.INFO.intValue() )
1593         {
1594             log( throwable, Project.MSG_INFO );
1595         }
1596         else
1597         {
1598             log( throwable, Project.MSG_DEBUG );
1599         }
1600     }
1601 
1602     /**
1603      * Logs a validation report.
1604      *
1605      * @param context The context to use for logging the report.
1606      * @param report The report to log.
1607      *
1608      * @throws NullPointerException if {@code context} or {@code report} is {@code null}.
1609      * @throws BuildException if logging fails.
1610      */
1611     public final void logValidationReport( final ModelContext context, final ModelValidationReport report )
1612     {
1613         try
1614         {
1615             if ( !report.getDetails().isEmpty() )
1616             {
1617                 this.logSeparator();
1618                 Marshaller marshaller = null;
1619 
1620                 for ( final ModelValidationReport.Detail detail : report.getDetails() )
1621                 {
1622                     this.logMessage( detail.getLevel(), "o " + detail.getMessage() );
1623 
1624                     if ( detail.getElement() != null )
1625                     {
1626                         if ( marshaller == null )
1627                         {
1628                             marshaller = context.createMarshaller( this.getModel() );
1629                             marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
1630                         }
1631 
1632                         final StringWriter stringWriter = new StringWriter();
1633                         marshaller.marshal( detail.getElement(), stringWriter );
1634                         this.logMessage( Level.FINEST, stringWriter.toString() );
1635                     }
1636                 }
1637             }
1638         }
1639         catch ( final ModelException e )
1640         {
1641             throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
1642         }
1643         catch ( final JAXBException e )
1644         {
1645             String message = Messages.getMessage( e );
1646             if ( message == null && e.getLinkedException() != null )
1647             {
1648                 message = Messages.getMessage( e.getLinkedException() );
1649             }
1650 
1651             throw new BuildException( message, e, this.getLocation() );
1652         }
1653     }
1654 
1655     /**
1656      * Creates and returns a copy of this object.
1657      *
1658      * @return A copy of this object.
1659      */
1660     @Override
1661     public JomcTask clone()
1662     {
1663         try
1664         {
1665             final JomcTask clone = (JomcTask) super.clone();
1666             clone.classpath = (Path) ( this.classpath != null ? this.classpath.clone() : null );
1667 
1668             if ( this.modelContextAttributes != null )
1669             {
1670                 clone.modelContextAttributes = new ArrayList<KeyValueType>( this.modelContextAttributes.size() );
1671 
1672                 for ( final KeyValueType e : this.modelContextAttributes )
1673                 {
1674                     clone.modelContextAttributes.add( e.clone() );
1675                 }
1676             }
1677 
1678             if ( this.transformationParameters != null )
1679             {
1680                 clone.transformationParameters =
1681                     new ArrayList<KeyValueType>( this.transformationParameters.size() );
1682 
1683                 for ( final KeyValueType e : this.transformationParameters )
1684                 {
1685                     clone.transformationParameters.add( e.clone() );
1686                 }
1687             }
1688 
1689             if ( this.transformationParameterResources != null )
1690             {
1691                 clone.transformationParameterResources =
1692                     new ArrayList<PropertiesResourceType>( this.transformationParameterResources.size() );
1693 
1694                 for ( final PropertiesResourceType e : this.transformationParameterResources )
1695                 {
1696                     clone.transformationParameterResources.add( e.clone() );
1697                 }
1698             }
1699 
1700             if ( this.transformationOutputProperties != null )
1701             {
1702                 clone.transformationOutputProperties =
1703                     new ArrayList<KeyValueType>( this.transformationOutputProperties.size() );
1704 
1705                 for ( final KeyValueType e : this.transformationOutputProperties )
1706                 {
1707                     clone.transformationOutputProperties.add( e.clone() );
1708                 }
1709             }
1710 
1711             return clone;
1712         }
1713         catch ( final CloneNotSupportedException e )
1714         {
1715             throw new AssertionError( e );
1716         }
1717     }
1718 
1719 }